Nisala and Jana: Final Final Final

Well, this has been quite a journey. Obviously, working together with Nisala was quite a lot of desperate laughing, excited high fives and cries for help that both Jack and Aaron patiently listened to- so big big thank you!

The IM show itself was a lot of fun. We managed to fix most of the bugs/imperfections that we caught during the user testing – we fixed the descriptions, colors, adjusted the mapped values and got an iMac to manage the load of data. Eventually, we decided for two colors on the screen only (red for the shapes that are being adjusted and submitted, and white for the already submitted ones). This helped to increase clarity for the user.

We adjusted the mapped values for volume right at the start of the show, so it would take into account the amount of noise in the room – which was quite significant. The only issue was the light – because we changed our location last minute into the corner behind a curtain (where people could scream privately and shamelessly). Therefore, we wanted a light leading people into the corner, but we did not consider that it would interfere with the readability of the descriptions. Me and Nisala’s ambition to be sustainable and laser-cut cardboard waste for the descriptions was a good step for the environment, however, in a dark corner, with the wrong angle of light, it did not facilitate the best conditions for the desired interaction. So, if installing this next time, this would be the first thing to carefully consider. Together with the height of the stand, since we unintentionally discriminated short people.

People, however, had a lot of fun screaming into the microphone. When they left the space, they were smiling a lot – and giving them the private corner to do so was definitely a good choice as they could loosen up a little more. This was quite a success as our intention was not only to collect data on how people feel at this stressful time of the year but also to relief a little of the stress.

Though to some people, a further explanation had to be given about the meaning of what they have seen on the screen, most people understood the concept. The interaction for most people was smooth – just getting them to actually read the instructions first instead of pressing random buttons was a bit of a challenge (but this is where better light could help us if displayed next time).

Here is the video of the final data that we collected from the show.

Here is our final setup:


Close up of the interface:

Sadly, people did not feel THAT much better to support our efforts financially (of course this was a joke – or wasn’t it?). They reacted to this humorous touch really well.

The Arduino code:

const int ledWHITE = 12;
const int ledRED = 13;
int buttonConfirmArdi = 8;
int buttonHoldArdi = 9;

int buttonConfirmState = 0;
int buttonHoldState = 0;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  Serial.println("0,0,0,0,0,0,0,0");
  pinMode(2, INPUT);
  pinMode(3, INPUT);
  pinMode(4, INPUT);
  pinMode(5, INPUT);
  pinMode(6, INPUT);
  pinMode(8, INPUT);
  pinMode(9, INPUT);
  pinMode(12, OUTPUT);
  pinMode(13, OUTPUT);

}

void loop() {
  if (Serial.available() > 0) {
    char inByte = Serial.read();
    delay(0);
    int sadButton = digitalRead(2);
    int fearButton = digitalRead(3);
    int joyButton = digitalRead(4);
    int angerButton = digitalRead(5);
    int disgustButton = digitalRead(6);

    int potentiometer = analogRead(A0);

    int buttonConfirm = digitalRead(8);
    int buttonHold = digitalRead(9);

    delay(0);
    Serial.print(sadButton);
    Serial.print(',');
    Serial.print(fearButton);
    Serial.print(',');
    Serial.print(joyButton);
    Serial.print(',');
    Serial.print(angerButton);
    Serial.print(',');
    Serial.print(disgustButton);
    Serial.print(',');

    Serial.print(potentiometer);
    Serial.print(',');
    Serial.print(buttonHold);
    Serial.print(',');
    Serial.println(buttonConfirm);


  }

  buttonConfirmState = digitalRead(buttonConfirmArdi);
  buttonHoldState = digitalRead(buttonHoldArdi);

  if (buttonConfirmState == HIGH) {
    // turn LED on:
    digitalWrite(ledRED, HIGH);
  } else {
    // turn LED off:
    digitalWrite(ledRED, LOW);
  }


  if (buttonHoldState == HIGH) {
    // turn LED on:
    digitalWrite(ledWHITE, HIGH);
  } else {
    // turn LED off:
    digitalWrite(ledWHITE, LOW);
  }
}

Processing Code:

//calling the class.

ArrayList<DrawCircle> circles;
ArrayList<DrawSquare> squares;
ArrayList<DrawTriangle> triangles;
ArrayList<DrawLine> lines;
ArrayList<DrawHexagon> hexagons;

//establishing the communication with arduino
import processing.serial.*;
Serial myPort;

//Emotion Buttons

int sadButton=0;
int prevSadButtonState=0;

int fearButton =0;
int prevFearButtonState =0;

int joyButton =0;
int prevJoyButtonState =0;

int angerButton =0;
int prevAngerButtonState =0;

int disgustButton =0;
int prevDisgustButtonState =0;


int potentiometer;


//For Button
int buttonState=0;
int prevButtonState = 0;

//for Held Button
int buttonStateHold = 0;

boolean confirmColor= false;

//Audio stuff, from mic.

import processing.sound.*;
Sound s;


AudioIn audioInput;
Amplitude analyzer;

float volume;

boolean readyToDraw;



void setup() {
  //size(640, 480);
  fullScreen();
  noFill();
  blendMode(ADD);
  //stuff for Arduino.
  printArray(Serial.list());
  String portname=Serial.list()[11];
  println(portname);

  myPort = new Serial(this, portname, 9600);
  myPort.clear();
  myPort.bufferUntil('\n');


  //AUDIO INITIALIZATION
  // Create an Audio input and grab the 1st channel
  audioInput = new AudioIn(this, 0);
  println("here is the input " + audioInput);
  // start the Audio Input
  audioInput.start();

  // create a new Amplitude analyzer
  analyzer = new Amplitude(this);
  // Patch the input to an volume analyzer
  analyzer.input(audioInput);


  //initialize the arrayList

  circles = new ArrayList <DrawCircle>();
  squares = new ArrayList <DrawSquare>();
  hexagons = new ArrayList <DrawHexagon>();
  lines = new ArrayList <DrawLine>();
  triangles = new ArrayList <DrawTriangle>();
}


void draw() {
  //if (confirmColor == false) {
  //    if (buttonState==1) {
  //        confirmColor=true;
  //        fill(255);
  //    } else {
  //      fill(0, 255, 0);
  //    } 
  //  }

  background(0);

  //  fill(20,30); 
  //rect(0,0, width,height);
  //pushStyle();
  //fill(100,50);
  //rect(0,0, height, width);
  //popStyle();

  // Get the overall volume (between 0 and 1.0)
  volume = analyzer.analyze();

  //update and display the circles, squares, etc.
  updateDisplayForLoop();

  //Only draw the shape that corresponds with the button.
  clickEmotionButton();
}




void serialEvent(Serial myPort) {
  String s =myPort.readStringUntil('\n');
  s=trim(s);
  if (s!=null) {
    int values[]=int(split(s, ','));
    sadButton = values[0];
    fearButton = values[1];
    joyButton = values[2];
    angerButton = values[3];
    disgustButton = values[4];
    potentiometer= values[5];
    buttonStateHold = values[6];
    buttonState= values[7];
  }
  println(sadButton, fearButton, joyButton, angerButton, disgustButton, potentiometer, buttonState, buttonStateHold);
  myPort.write(0);
}



void clickEmotionButton() {

  //ONLY DRAW THE CIRCLE IF THE SAD BUTTON PRESSED.
  if (prevSadButtonState==1 && sadButton==0) {

    circles.add(new DrawCircle());

    if (prevButtonState==1 && buttonState==0) {
      fill(0, 0, 255, 40);
      circles.add(new DrawCircle());
    }
  } 


  //ONLY DRAW THE SQUARE IF THE FEARBUTTON PRESSED

  if (prevFearButtonState==1 && fearButton==0) {

    squares.add(new DrawSquare());
    if (prevButtonState==1 && buttonState==0) {
      squares.add(new DrawSquare());
    }
  }

  //ONLY DRAW THE HEXAGON IF THE JOYBUTTON PRESSED
  if (prevJoyButtonState==1 && joyButton==0) {
    hexagons.add(new DrawHexagon());

    if (prevButtonState==1 && buttonState==0) {
      hexagons.add(new DrawHexagon());
    }
  }

  //ONLY DRAW THE LINE IF THE ANGERBUTTON PRESSED
  if (prevAngerButtonState==1 && angerButton==0) {
    lines.add(new DrawLine());

    if (prevButtonState==1 && buttonState==0) {
      lines.add(new DrawLine());
    }
  }


  //ONLY DRAW THE TRIANGLE IF THE DISGUSTBUTTON PRESSED
  if (prevDisgustButtonState==1 && disgustButton==0) {
    triangles.add(new DrawTriangle());

    if (prevButtonState==1 && buttonState==0) {
      triangles.add(new DrawTriangle());
    }
  }


  prevSadButtonState= sadButton;
  prevFearButtonState= fearButton;
  prevJoyButtonState = joyButton;
  prevAngerButtonState = angerButton;
  prevDisgustButtonState = disgustButton;
  prevButtonState = buttonState;
}


void updateDisplayForLoop() {


  for (DrawCircle circle : circles) {
    circle.update(potentiometer, buttonState, buttonStateHold);
    fill(255, 40);
    circle.display();
  }


  for (DrawLine line : lines) {
    line.update(potentiometer, buttonState, buttonStateHold);
    line.display();
  }

  for (DrawSquare square : squares) {
    square.update(potentiometer, buttonState, buttonStateHold);
    square.display();
  }

  for (DrawHexagon hexagon : hexagons) {
    hexagon.update(potentiometer, buttonState, buttonStateHold);
    hexagon.display();
  }


  for (DrawTriangle triangle : triangles) {
    triangle.update(potentiometer, buttonState, buttonStateHold);
    triangle.display();
  }
}

class DrawCircle {

  PVector center;
  float angle;
  float radius;// radius if rotation

  float circleSize;

  float x;
  float y;
  float lineWeight;

  float energyLevel;

  boolean confirm;


  float speedModifier;


  //the center oof the imaginary circle
  int centerX; 
  int centerY;

  int circleColor;
  int circleStrokeColor;


  DrawCircle() {


    centerX= width/2 + width/3; 
    centerY = height/2-height/16;

    center = new PVector(centerX, centerY);
    radius = 300;
    circleSize = 30;
    lineWeight=1;
    speedModifier=120;
    confirm=false;
    circleColor = color(216, 29, 68); 
    circleStrokeColor=color(216, 29, 68);
    ;
  }

  void update(int _potentiometer, int buttonState, int buttonStateHold) {


    //if (confirmVolume==true) {

    //  if(button2State==)
    //}



    if (confirm==false) {
      if (buttonState==1) {
        confirm=true;
        circleColor= color(255, 255, 255, 70);
        circleStrokeColor=color(255);
      }

      radius = map(_potentiometer, 0, 1023, 100, 50);//radiusModifier

      speedModifier = map(_potentiometer, 0, 1023, 150, 5);

      if (buttonStateHold==1) {   //If the button is held, then change these things.
        lineWeight = map(volume, 0, 1, 1, 20);
        circleSize = map(volume, 0, 1, 10, 1000);
      }
    }

    x = center.x + cos(angle)*radius;
    y = center.y + sin(angle)*radius;

    angle += PI/speedModifier; // speedModifier
  }


  void display() {
    fill(circleColor);
    stroke(circleStrokeColor);
    noStroke();
    //strokeWeight(3);
    ellipse(x, y, circleSize, circleSize);
  }
}

class DrawHexagon {

  PVector center;
  float angle;
  float radius;// radius if rotation

  float scaleSize;

  float x;
  float y;
  float lineWeight;

  float energyLevel;

  boolean confirm;


  float speedModifier;


  //the center oof the imaginary circle
  float centerX; 
  float centerY;
  
  int hexagonColor;
  int hexagonStrokeColor;

  DrawHexagon() {


    centerX= width/2 - width/6; 
    centerY = height/2 +height/3.5;

    center = new PVector(centerX, centerY);
    radius = 300;
    scaleSize = 10;
    lineWeight=1;
    speedModifier=120;
    confirm=false;
    hexagonColor = color(216, 29, 68);
    hexagonStrokeColor = color(216, 29, 68);
    
  }

  void update(int _potentiometer, int buttonState, int buttonStateHold) {


    if (confirm==false) {
      if (buttonState==1) {
        confirm=true;
        hexagonColor = color(255, 255, 255, 70);
        hexagonStrokeColor = color(255);
      }

      radius = map(_potentiometer, 0, 1023, 100, 50);//radiusModifier

      speedModifier = map(_potentiometer, 0, 1023, 150, 5);

      if (buttonStateHold==1) {   //If the button is held, then change these things.
        lineWeight = map(volume, 0, 1, 1, 20);
        scaleSize = map(volume, 0, 1, 10, 500);
      }
    }

    x = center.x + cos(angle)*radius;
    y = center.y + sin(angle)*radius;

    angle += PI/speedModifier; // speedModifier
  }


  void display() {
    fill(hexagonColor);
    stroke(hexagonStrokeColor);
    noStroke();
    //strokeWeight(3);


    pushMatrix();
    translate(x, y);
    scale(scaleSize/50);
    beginShape();
    vertex(0 - 44, 0 - sqrt(3) * 44);
    vertex(0 + 44, 0 - sqrt(3) * 44);
    vertex(0 + 2 * 44, 0);
    vertex(0 + 44, 0 + sqrt(3) * 44);
    vertex(0 - 44, 0 + sqrt(3) * 44);
    vertex(0 - 2 * 44, 0);
    endShape(CLOSE);
    popMatrix();
  }
}

class DrawLine {

  PVector center, point1, point2;
  float angle;
  float radius;// radius if rotation

  float circleSize;

  float x1, x2;
  float y1, y2;
  float lineWeight;

  float energyLevel;

  boolean confirm;


  float speedModifier;


  //the center oof the imaginary circle
  float centerX; 
  float centerY;
  
  int lineColor;



  DrawLine() {
    point1 = new PVector(x1, y1);
    point2 = new PVector(x2, y2);

    centerX= width/2 + width/6; 
    centerY = height/2 + height/3.5;

    center = new PVector(centerX, centerY);
    radius = 300;
    circleSize = 10;
    lineWeight=10;
    speedModifier=120;
    confirm=false;
    
    lineColor = color(216, 29, 68);
  }

  void update(int _potentiometer, int buttonState, int buttonStateHold) {

    if (confirm==false) {
      if (buttonState==1) {
        confirm=true;
        lineColor = color(255, 255, 255, 90);
      }

      radius = map(_potentiometer, 0, 1023, 100, 50);//radiusModifier

      speedModifier = map(_potentiometer, 0, 1023, 150, 5);

      if (buttonStateHold==1) {   //If the button is held, then change these things.
        lineWeight = map(volume, 0, 1, 3, 5);
        circleSize = map(volume, 0, 1, 10, 500);
      }
    }






    x1 = center.x + cos(angle)*radius;
    y1 = center.y + sin(angle)*radius;


    angle += PI/speedModifier; // speedModifier
  }


  void display() {
    //fill(lineColor);
    stroke(lineColor);
    //noStroke();
    strokeWeight(5);
    //strokeWeight(5);

    pushMatrix();
    translate(x1, y1);
    scale(circleSize/20);
    line(0, 0, 60, 0);
    popMatrix();
  }
}

class DrawSquare {

  PVector center;
  float angle;
  float radius;// radius if rotation

  float squareSize;

  float x;
  float y;
  float lineWeight;

  float energyLevel;

  boolean confirm;


  float speedModifier;
  
  //the center oof the imaginary circle
  int centerX; 
  int centerY;
  
  
  int squareColor;
  int squareStrokeColor;

  DrawSquare() {

    centerX=width/2- width/3; // the place where the object is drawn changes depending on what we input.
    centerY=height/2-height/16;
    
    center = new PVector(centerX, centerY);
    squareSize = 10;
    lineWeight=1;
    speedModifier=120;
    confirm=false;
    squareColor = color(216, 29, 68);
    squareStrokeColor = color(216, 29, 68);
  }

  void update(int _potentiometer, int buttonState, int buttonStateHold) {


    if (confirm==false) {
      if (buttonState==1) {
        confirm=true;
        squareColor = color(255, 255, 255, 70);
        squareStrokeColor = color(255);
      }

      radius = map(_potentiometer, 0, 1023, 100, 50);//radiusModifier
      speedModifier = map(_potentiometer, 0, 1023, 150, 5);
      
      
      
      if (buttonStateHold==1) {   //If the button is held, then change these things.
      lineWeight = map(volume, 0, 1, 1, 20);

      squareSize = map(volume, 0, 1, 10, 1000);
    }
    }


    x = center.x + cos(angle)*radius;
    y = center.y + sin(angle)*radius;

    angle += PI/speedModifier; // speedModifier
  }


  void display() {
    rectMode(CENTER);
    fill(squareColor);
    stroke(squareStrokeColor);
    noStroke();
    //strokeWeight(3);
    rect(x, y, squareSize, squareSize);
  }
}

class DrawTriangle{

  PVector center;
  float angle;
  float radius;// radius if rotation

  float circleSize;

  float x1, x2, x3;
  float y1, y2, y3;
  float lineWeight;

  float energyLevel;

  boolean confirm;


  float speedModifier;


  //the center oof the imaginary circle
  int centerX; 
  int centerY;
  
  int triangleColor;
  int triangleStrokeColor;

  DrawTriangle() {


    centerX= width/2; 
    centerY = height/2-height/3;

    center = new PVector(centerX, centerY);
    radius = 300;
    circleSize = 10;
    lineWeight=1;
    speedModifier=120;
    confirm=false;
    triangleColor = color(216, 29, 68);
    triangleStrokeColor = color(216, 29, 68);
  }

  void update(int _potentiometer, int buttonState, int buttonStateHold) {


    if (confirm==false) {
      if (buttonState==1) {
        confirm=true;
        triangleColor = color(255, 70);
        triangleStrokeColor = color(255);
      }

      radius = map(_potentiometer, 0, 1023, 100, 50);//radiusModifier

      speedModifier = map(_potentiometer, 0, 1023, 150, 5);

      if (buttonStateHold==1) {   //If the button is held, then change these things.
        lineWeight = map(volume, 0, 1, 1, 20);
        circleSize = map(volume, 0, 1, 10, 500);
      }
    }


    x1 = center.x + cos(angle)*radius;
    y1 = center.y + sin(angle)*radius;
    
    

    angle += PI/speedModifier; // speedModifier
  }


  void display() {
    fill(triangleColor);
    noStroke();
   // stroke(triangleStrokeColor);
    
    //strokeWeight(1);
    
    pushMatrix();
    translate(x1, y1);
    scale(circleSize/50);
    triangle(0, 0, 0, 90, 90, 0);
    popMatrix();
  }
}

 

Final Project

After all the mental breakdowns and the literal physical pain, this semester has come to a close. My final project wanted to raise some sort of awareness on the environment and on the dangers of consumption and greed. Although it did come out a bit more comical than I intended it to,  I think my final project was a success as it made people laugh, have fun and more importantly, think.

These are all the paintings for the interface, done with watercolors:

The box went from this:

To this:

I still think it would’ve looked nicer with wood, but for something DIY-ed I think it looks really good.

This is the code for my final project, never thought I’d say this in my life but I’m glad the code got deleted because I actually learned a lot while working and figuring things out. The funniest thing was attempting to add the sounds to the program last minute, and it actually worked!!!!

import processing.sound.*;
SoundFile beep;
SoundFile happy;
SoundFile scary;

import processing.video.*;
import jp.nyatla.nyar4psg.*;


PImage background;
PImage background2;
PImage tree;
PImage tree2;
PImage bush;
PImage mushroom;
PImage factory;
PImage waterBottle;
PImage TrashCan;
PImage bags;
PImage bear;
PImage dBear;
PImage lily;
PImage lily2;
PImage fish1;
PImage fish2;
PImage fish3;
PImage deadFish1;
PImage deadFish2;
PImage deadFish3;
PImage bird;
PImage bird2;
PImage bird3;
PImage bird4;
PImage deadBirdPic; 
PImage deadBirdPic2;


Movie Apocalypse;


int value = 0;


Capture cam;


MultiMarker nya;


int[] objects = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
int i = objects.length;


boolean b = true;
boolean backgroundB = false;
boolean factoryPic = false;
boolean trashCan = false;
boolean bag = false;
boolean bottle =false;
boolean apocalypse = false;
boolean deadBird = true; 
boolean treesGone = true; 
boolean deadBear = true;
boolean fish = true; 
boolean Badmusic = false;

void setup() {
  //size(1920, 1080, P3D);
  fullScreen(P3D, 2);
  colorMode(RGB, 100);
    String[] cameras = Capture.list();
  //printArray(cameras);
  cam = new Capture(this, cameras[0]); 
  //cam=new Capture(this, 1280,700);
  nya=new MultiMarker(this, 1920, 1080, "camera_para.dat", NyAR4PsgConfig.CONFIG_PSG);
  
  Apocalypse = new Movie(this, "fire.mov");
  
    beep = new SoundFile(this, "beep.wav");
    scary = new SoundFile(this, "thunder.wav");
    happy = new SoundFile(this, "nature.wav"); 
    
  background = loadImage("background.png");
  background2 = loadImage("background2.png");
  
  factory = loadImage("factory4.png");
  TrashCan = loadImage("trashcan.png");
  bags = loadImage("bags.png");
  waterBottle = loadImage("waterBottle.png");
  

  tree = loadImage("tree.png");
  bush = loadImage("bush.png");
  tree2 = loadImage("tree2.png");
  bear = loadImage("bear.png");
  dBear = loadImage("deadBear.png");
  
  bird = loadImage("bird.png");
  bird2 = loadImage("bird2.png");
  bird3 = loadImage("bird3.png");
  bird4 =loadImage ("bird4.png");
  deadBirdPic = loadImage("deadBird.png");
  deadBirdPic2 = loadImage("deadBird2.png");
  
  fish1 = loadImage("fish1.png");
  fish2 = loadImage("fish2.png");
  fish3 = loadImage("fish3.png");
  deadFish1 = loadImage("deadfish1.png");
  deadFish2 = loadImage("deadfish2.png");
  deadFish3 = loadImage("deadfish3.png");
  lily = loadImage("lily1.png");
  lily2 = loadImage("lily2.png");
  
  
  fish1.resize(80,100);
  fish2.resize(80,100);
  fish3.resize(100,80);
  deadFish1.resize(120,80);
  deadFish2.resize(120,80);
  deadFish3.resize(120,80);
  
  tree.resize(600, 750);
  tree2.resize(500,600);
  bush.resize(200,400);
  
  bear.resize(280, 400);
  dBear.resize(450,180);
  
  bird.resize(200, 200);
  bird2.resize(200, 200);
  bird3.resize(200,100);
  bird4.resize(200,200);
  deadBirdPic.resize(200,100);
  deadBirdPic2.resize(300,100);
  factory.resize(350, 550);
  TrashCan.resize(400,600);
  bags.resize(500,400);
  //dBear.resize(,)
  factory.resize(400, 600);
  for (int c = 0; c < i; c++) {
    nya.addNyIdMarker(c, 80);
  }

  cam.start();
  background(background);
  noCursor();
}

void draw() {
  if (cam.available()!=true) {
    return;
  }
  cam.read();
  nya.detect(cam);
  background(background);

  image(Apocalypse, 0, 0);

  //if (b) {
  //  pushStyle();
  //  imageMode(CORNER);
  //  pushMatrix();
  //  //translate(1280/2,700/2);
  //  nya.drawBackground(cam);
  //  popMatrix();
  //  popStyle();
  //}

  //Apocalypse.resize(displayWidth,displayHeight); how do i make the video fullscreen?
  //use some app and rezise and change the Apocalypse video before class on Monday please please please Dhabiaaaa
  
  for (int c = 0; c < i; c++) {
    if ((!nya.isExist(c))) {
      //println(nya.getNyId(c));
      
      //println("returning");
      continue;
    }

    if (c==1) {
      if (!beep.isPlaying() && deadBear==true){
        beep.play();
      }
      else{
        beep.stop();
      }
      //Bear turns to carpet code 
      deadBear = false;
      break;
    }
    
    if (c==2 && treesGone == true) {
       treesGone = false;
      if (!beep.isPlaying()){
        beep.play();
      }
      else{
        beep.stop();
      }   //tree falls code because of table or chair or maybe both????
    }
    
    if (c==3 && trashCan == false) {
      if (!beep.isPlaying()){
        beep.play();
      }
      else{
        beep.stop();
      }
      trashCan = true;
     }
          
    if (c==4&& factoryPic==false) {
      factoryPic = true;
      if (!beep.isPlaying()){
        beep.play();
      }
      else{
        beep.stop();
      }
    }

    if (c==5 && fish == true) {
      if (!beep.isPlaying()){
        beep.play();
      }
      else{
        beep.stop();
      }      //fish
      fish = false;
    }
    
    if (c==6 && deadBird == true) {
      if (!beep.isPlaying()){
        beep.play();
      }
      else{
        beep.stop();
      }      //bird dies 
      deadBird = false;
    }
    
    if (c==7 && bag == false) {
      if (!beep.isPlaying()){
        beep.play();
      }
      else{
        beep.stop();
      }      bag = true;
    }
    
    
    if (c==8 && backgroundB ==false) {
      if (!beep.isPlaying()){
        beep.play();
      }
      else{
        beep.stop();
      }      backgroundB = true;
    }
    
    if (c==9) {
      
    }
    
    if (c==0 && apocalypse == false) {
      if (!beep.isPlaying()){
        beep.play();
      }
      else{
        beep.stop();
      }      //don't forget to rezie the video + add a text that says, real life has no reset button
      Apocalypse.loop();
      apocalypse=true;
    }
    

  }


  
  if (backgroundB){
    background(background2);
    
  }
  
  if (bag && apocalypse == false){
    image(bags, 1100, 250); //fix
  }
  

  
  if (!deadBird && apocalypse==false){
    image(deadBirdPic, 80,780);
    image(deadBirdPic2, 600, 450);
  }
  if (deadBird && apocalypse==false){
  image(bird, 800, 100);
  image(bird2, 100, 50);
  image(bird3, 1000, 50);
  image(bird4, 500, 50);
  }
  if (fish && apocalypse == false){
    image(fish1,700,750);
    image(fish2,500,740);
    image(fish3,900,730);
    image(lily,1000,740);
    image(lily2,800,720);

  }
  if (!fish && apocalypse == false){
    image(deadFish1,700,750);
    image(deadFish2,1100,730);
    image(deadFish3,900,730);
  }
  if (treesGone && apocalypse == false){
  image(tree2,1000,100);    
  image(tree, 1300, 0);
  image(bush,1300,500);
  //Add more trees
  }
  if (deadBear && apocalypse == false){
   image(bear, 250, 600);  
  }

  
  if (trashCan && apocalypse == false){
    image(TrashCan, 1400, 550); //fix

  } 
  
    if (factoryPic) {
    image(factory, 0, 0);
  }
  
    if (!deadBear && apocalypse == false){
    image(dBear,100,500);
  }
  
  if (!apocalypse){
    if (!happy.isPlaying()){
      happy.loop();
    }
  }
  
  if (apocalypse) {
    if (happy.isPlaying()){
      scary.loop();
    if (scary.isPlaying()){
      happy.stop();
    }
      
    }
    image(Apocalypse, 0, 0);
    textSize(80);
    text("Real life has no reset button", 50, 80);
    factoryPic = false;
    treesGone = false; 
    deadBird=true; 

  }
}


void keyPressed() {
  if (keyPressed) {
    if (key == 'b' || key == 'B') {
      b = !b;
    }
  }
}


void movieEvent(Movie m) {
  m.read();
}

 

Final project documentation. Maxim Blinov

Here it goes… 🙂

My final project idea was to get something from an alternate reality. After some brainstorming, I came up with this idea to make this arcade claw game (everyone knows this when this game where it is almost impossible to pull out an item) and make it virtual (a.k.a. on the computer) and make the prize be real. So it turned out to be this:

Description of the software/hardware:
The game itself consists of a big script made in processing which drew the claw machine game (code will be attached in the end) and an arduino script which opened the door and gave the prize. Sounds simple, but it took a lot of effort to do it. The claw was controlled with a joystick and the goal of the game was to drop an item off screen.

Expectation/evaluation:
The expectation was to get an item from the alternate reality and it worked out pretty well 🙂 The prizes were some haribos and chocolate chip cookies

Code:
Arduino code:

#include <Servo.h>;
int left = 0;
int right = 0;
int servoPin1 = 3;
int servoPin2 = 4;
const int inX = A1;
const int pressed = 6;
int xValue = 0;
int notPressed = 0;
Servo Servo1;
Servo Servo2;
void setup() {
  pinMode(inX, INPUT); // setup y input
  pinMode(pressed, INPUT_PULLUP); // we use a pullup-resistor for the button functionality
  Servo1.attach(servoPin1);
  Servo2.attach(servoPin2);
  Serial.begin(9600);
  Serial.println("1,1");
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
}

void loop() {

  xValue = analogRead(inX);
  notPressed = digitalRead(pressed);
  Serial.print(xValue);
  Serial.print(",");
  Serial.println(notPressed);
  while (Serial.available()) {

    right = Serial.parseInt();
    left = Serial.parseInt();

    if (Serial.read() == '\n') {
      Servo1.write(right);
      Servo2.write(left);
    }


  }

}

    Processing code:
main:

import shiffman.box2d.*;
import org.jbox2d.common.*;
import org.jbox2d.dynamics.joints.*;
import org.jbox2d.collision.shapes.*;
import org.jbox2d.collision.shapes.Shape;
import org.jbox2d.common.*;
import org.jbox2d.dynamics.*;
import org.jbox2d.dynamics.contacts.*;
import processing.serial.*;

Box2DProcessing box2d;
Claw claw;
bounds bounds;
movement move;
Serial myPort;
float moveX = 0;
float moveY = 0;
int directionX = 0;
int directionY = 0;
float gravity = 1;
float ellipseX = 200;
float ellipseX1 = 600;
float ellipseY = 400;
float ellipseY1 = 400;
int s;
int current = 0;
boolean time = false;
boolean pickup = false;
boolean attach = false;
boolean attach1 = false;
boolean ball1_out = true;
boolean ball2_out = true;
PImage img1,img2;

void setup() {
  //size(1280,1024);
  fullScreen();
  smooth();
  imageMode(CENTER);
  claw = new Claw();
  bounds = new bounds();
  move = new movement();
  printArray(Serial.list());
  img1 = loadImage("/home/neonovi/Desktop/cookie.png");
  img2 = loadImage("/home/neonovi/Desktop/snake.png");
  String portname=Serial.list()[32];
  println(portname);
  myPort = new Serial(this, portname, 9600);
  myPort.clear();
  myPort.bufferUntil('\n');
  myPort.write("1,1");
}


void keyPressed() {
  pickup=false;
  if (key == 'd' && moveY==0)
    directionX = 5;
  else if (key == 'a' && moveY==0)
    directionX = -5;
  else if (keyCode == 32) {
    attach = false;
    attach1 = false;
    if (moveY < height-300)
      directionY=3;
    claw.add = 0.01;
  }
}


void keyReleased() {
  directionY=-3;
  directionX=0;
  claw.add=-0.01;
}


void draw() {
  background(255);
  move.display();
  claw.display(moveX, 150+moveY); 
  fill(0);
  if (ellipseY<height-76 && !attach || ellipseX > width-150 && !attach)
    ellipseY+=gravity;
  if (ellipseY1<height-76 && !attach1 || ellipseX1 > width-150 && !attach1)
    ellipseY1+=gravity;
  image(img1,ellipseX, ellipseY, 100, 80);
  image(img2, ellipseX1, ellipseY1, 100,80);
  if (ellipseY >= height)
    ball1_out = false;
  if (ellipseY1 >= height)
    ball2_out = false;
  bounds.display();
  //image(img1,0,0,100,80);
  myPort.write(135*int(ball1_out)+","+135*int(!ball2_out)+"\n");
  println("ellipseY = "+ellipseY+" | moveY = "+moveY);//135*int(!ball1_out)+","+135*int(ball2_out)+"\n");
  println("ellipseX = "+ellipseX+" | moveX = "+moveX);
}

boundries:

class bounds{
 
void display(){
  rect(0, height-50, width-200, 50);
  rect(width-200, height-100, 50, 100); 
}
}

claw:

class Claw {
  float x=0;
  float y=0;
  float z=0;
  float add=0;
  Claw() {
  }

  void display(float x, float y) {
    fill(255);
    //ellipse(x+5, y+50, 100, 100);
    fill(0);
    rect(x, -1024+y, 10, 1024);
    pushMatrix();
    translate(x, y-5);
    rotate(z%TWO_PI);
    beginShape();
    vertex(0, 0);
    vertex(10, 0);
    vertex(-30, 50);
    vertex(0, 100);
    vertex(-50, 50);
    endShape();
    popMatrix();
    pushMatrix();
    translate(x+10, y-5);
    rotate(-z%TWO_PI);
    beginShape();
    vertex(0, 0);
    vertex(0-10, 0);
    vertex(30, 50);
    vertex(0, 100);
    vertex(50, 50);
    endShape();
    popMatrix();
    if (z<0.6 && add>0)
      z+=add;
    if (z>0 && add<0)
      z+=add;
  }
}

movement:

class movement {



  void display() {
    if (moveY<=height-301)
      moveY += directionY;
    moveX += directionX;
    if (moveY<0) {
      directionY=0;
      moveY=0;
    }
      if (moveY>height-300) {
    directionY=0;
    if (!time) {
      current = millis();
      time = true;
    }
    if (millis()-current>1500) {
      time=false;
      pickup=true;
    }
  }
  if (pickup) {

    if (moveY > 0) {
      moveY-=2;
      if (moveX >= ellipseX-25 && moveX<=ellipseX+25 && (ellipseY-moveY)<230) {
        attach = true;
        gravity=0;
        ellipseY = moveY+220;
      }
      else if (moveX >= ellipseX1-25 && moveX<=ellipseX1+25 && (ellipseY1-moveY)<230) {
        attach1 = true;
        gravity=0;
        ellipseY1 = moveY+220;
      }
    } else
      moveY=0;
  }
  gravity+=0.1;
  if (moveX>1280) {
    moveX = 0;
  }
  if (moveX<0) {
    moveX = 1280;
  }
  if (attach) {
    ellipseX=moveX+5;
    gravity=0;
    ellipseY=moveY+220;
  }
  else if (attach1) {
    ellipseX1=moveX+5;
    gravity=0;
    ellipseY1=moveY+220;
  }
  else
  gravity = 5;
  }
}

 

 

 

 

Final Project Showcase

For my final project, I wanted to create an interactive museum exhibit from the distant future, that describes and showcases our current technology in the 2010s. I specifically chose to talk about the iPhone and its uses as well as the culture around our smartphone devices and apps such as Snapchat.

To do this, I had two parts. The first was a pedestal with the only remaining artifact from Apple in the future, an  iPhone:

For this part, I used a servo motor attached with a finger made of cork and a cotton bud that is connected to electricity to show how people “used to” have to physically control their devices using their fingers. At first, I hoped that the motor would be triggered by people’s movement in front of an IR sensor, but seeing as the sensor could not always detect the range of distance accurately and there was a lot of movement in front of it, I chose to have the motor rotate continuously instead. Here’s the simple Arduino code I used for that:

#include "Servo.h"

float distance = 0;

int pos = 0;
 
Servo myservo;
 
void setup()
{
  Serial.begin (9600);

  myservo.attach(10);
 
}
 
void loop() {
  //read distance
  distance = analogRead(A0);
  Serial.println("someone there");
 
  if (distance > 600) {  //close distance
    for (pos = 0; pos <= 100; pos += 1) {
// goes from 0 degrees to 180 degrees // in steps of 1 degree
myservo.write(pos); // tell servo to go to position in variable 'pos'
delay(15); // waits 15ms for the servo to reach the position
}
for (pos = 100; pos >= 0; pos -= 1) {
// goes from 180 degrees to 0 degrees
myservo.write(pos); // tell servo to go to position in variable 'pos'
delay(15); // waits 15ms for the servo to reach the position
}
  }
}

It was also connected to a Kinect device, which played a soundtrack where I gave a “tour” of the exhibition. The soundtrack played depending on how close the viewer is to the exhibit. However, this was one of the challenges that I faced during the showcase, because the space was very loud and even with speakers, the soundtrack was not loud enough. I replaced the speakers with headphones, but ideally, I would have liked them to play based on the viewer’s motion within the space. I had two separate soundtracks for each part of the exhibit, and using maximum and minimum depth thresholds as well as if statements to determine the distance, the code worked in a way that it controlled which soundtrack played based on where people were standing:

// Credits: Daniel Shiffman, Depth thresholding example

import org.openkinect.freenect.*;
import org.openkinect.processing.*;
import processing.sound.*;

Kinect kinect;

SoundFile soundfile;
SoundFile soundfile2;
// Depth image
PImage depthImg;

//soundtrack playing
boolean playing = false;

// pixels to be shown
//int minDepth =  100;
//int maxDepth = 900;


int maxDepth = 950;
int minDepth = 60;

// kinect's angle
float angle;
float L;

void setup() {
  size(1280, 480);

  kinect = new Kinect(this);
  kinect.initDepth();
  angle = kinect.getTilt();

  // blank image showing everything
  depthImg = new PImage(kinect.width, kinect.height);

  //load soundtrack
  soundfile = new SoundFile(this, "iPhone.aif");
  soundfile2 = new SoundFile (this, "Snapchat.aif"); 
  //println("Duration= " + soundfile.duration() + " seconds");
  //L=soundfile.duration();
  // for (int i = 0; i < files.length; i++) {
  //  files[i] = new SoundFile(this, (i+1) + ".aif");
  //}
}

void draw() {
  // draw the raw image
  image(kinect.getDepthImage(), 0, 0);

  // threshold the depth image
  int[] depth = kinect.getRawDepth();
  int counter =0;
  int left_counter =0, right_counter = 0;
  for (int x = 0; x < kinect.width; x++) {
    for (int y = 0; y < kinect.height; y++) {

      int offset =  x + y*kinect.width;
      // Grabbing the raw depth
      int rawDepth = depth[offset];

      if (rawDepth >= minDepth && rawDepth <= maxDepth && y<345  && y >170) {
        depthImg.pixels[offset] = color(255);
        
        if (x<240){

          right_counter++;
        }
        if (x>260 && x<500){

          left_counter++;
        }
      } else {
        depthImg.pixels[offset] = color(0);
      }
    }
  }
  //println(mouseX);
 
  //if (left_counter > 3600 || right_counter > 3600){

  //  if (!playing)
  //  {
  //    soundfile.play();
  //    playing = true;
  //  }
  //}

  //if (left_counter <= 3600 && right_counter <= 3600)
  //{
  //  if (playing)
  //  {
  //    soundfile.stop();
  //    playing = false;
  //  }
  //}
  if (left_counter > 3000)
  {
    if (!playing)
    {
      soundfile.play();
      playing = true;
    }
  } else
  {
    if (playing)
    {
      soundfile.stop();
      playing = false;
    }
  }
  
  //if (left_counter > 3000)
  //{
  //  if (!playing)
  //  {
  //    soundfile2.play();
  //    playing2 = true;
  //  }
  //} else
  //{
  //  if (playing)
  //  {
  //    soundfile.stop();
  //    playing = false;
  //  }
  //}

  // Draw the thresholded image
  depthImg.updatePixels();
  image(depthImg, kinect.width, 0);

  fill(0);
  text("TILT: " + angle, 10, 20);
  text("THRESHOLD: [" + minDepth + ", " + maxDepth + "]", 10, 36);
}

For the second part of the exhibit, I wanted to create some form of a Snapchat “simulator”, where people in the future could learn about one of the first ways in which facial recognition was used in a social media setting, as well as its influences in popular culture and so on. I had to try several examples and libraries on Processing, one of which used FaceOSC:

Here’s the initial code I tried working with on Processing:

// Face It
// Daniel Shiffman
// FaceOSC Example
// Adapted from Greg Borenstein: https://gist.github.com/atduskgreg/1603230

// Use with: https://github.com/downloads/kylemcdonald/ofxFaceTracker/FaceOSC.zip

import oscP5.*;
OscP5 oscP5;

import processing.video.*;

Capture cam;

PVector posePosition;
PVector poseOrientation;

boolean found;
float eyeLeftHeight;
float eyeRightHeight;
float mouthHeight;
float mouthWidth;
float nostrilHeight;
float leftEyebrowHeight;
float rightEyebrowHeight;

float poseScale;

PImage img;
PImage img2;


void setup() {
  size(640, 480);
  frameRate(30);

  img = loadImage("nose.png");
  img2 = loadImage("tongue.png");


  String[] cameras = Capture.list();

  if (cameras == null) {
    println("Failed to retrieve the list of available cameras, will try the default...");
    cam = new Capture(this, 640, 480);
  } 
  if (cameras.length == 0) {
    println("There are no cameras available for capture.");
    exit();
  } else {
    println("Available cameras:");
    printArray(cameras);

    // The camera can be initialized directly using an element
    // from the array returned by list():
    cam = new Capture(this, cameras[0]);
    // Or, the settings can be defined based on the text in the list
    //cam = new Capture(this, 640, 480, "Built-in iSight", 30);

    // Start capturing the images from the camera
    cam.start();
  }

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

  oscP5 = new OscP5(this, 8338);
  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");
  oscP5.plug(this, "eyeLeftReceived", "/gesture/eye/left");
  oscP5.plug(this, "eyeRightReceived", "/gesture/eye/right");
  oscP5.plug(this, "jawReceived", "/gesture/jaw");
  oscP5.plug(this, "nostrilsReceived", "/gesture/nostrils");
  oscP5.plug(this, "found", "/found");
  oscP5.plug(this, "poseOrientation", "/pose/orientation");
  oscP5.plug(this, "posePosition", "/pose/position");
  oscP5.plug(this, "poseScale", "/pose/scale");
}


void draw() {
  background(0);
  stroke(0);
  if (cam.available() == true) {
    cam.read();
  }
  imageMode(CORNER);
  image(cam, 0, 0);
  println(poseScale);
  if (found) {
    translate(posePosition.x, posePosition.y);
    //translate(width/2, height/2);
    scale(poseScale*.5, poseScale*.5);



    noFill();

    //ellipse(0,0, 3,3);
    //stroke (250);
    //rect(-20, eyeLeftHeight * -9, 20, 7);
    //rect(20, eyeRightHeight * -9, 20, 7);

    imageMode(CENTER);
    //image(img, 0, nostrilHeight * -1, 7, 3);
    image(img, 0, nostrilHeight * -1, 40, 30);

    //image(img2, 0, 20, mouthWidth* 3, mouthHeight * 3);
    image(img2, 0, 20, 30, 30);
    //image (img, 5, nostrilHeight * -1, 7, 3);
    //rectMode(CENTER);
    ////fill(0);
    //rect(-20, leftEyebrowHeight * -5, 25, 5);
    //rect(20, rightEyebrowHeight * -5, 25, 5);
  }
}

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 h) {
//  //println("eyebrow left: " + h);
//  leftEyebrowHeight = h;
//}

//public void eyebrowRightReceived(float h) {
//  //println("eyebrow right: " + h);
//  rightEyebrowHeight = h;
//}

//public void eyeLeftReceived(float h) {
//  //println("eye left: " + h);
//  eyeLeftHeight = h;
//}

//public void eyeRightReceived(float h) {
//  //println("eye right: " + h);
//  eyeRightHeight = h;
//}

public void jawReceived(float h) {
  //println("jaw: " + h);
}

//public void nostrilsReceived(float h) {
//  //println("nostrils: " + h);
//  nostrilHeight = h;
//}

public void found(int i) {
  //println("found: " + i); // 1 == found, 0 == not found
  found = i == 1;
}

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

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

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


void oscEvent(OscMessage theOscMessage) {
  if (theOscMessage.isPlugged()==false) {
    //println("UNPLUGGED: " + theOscMessage);
  }
}

But seeing as face detection was really slow using FaceOSC and Processing, Aaron suggested I use one of the demos from a collection of code written using Jeeliz, a “Javascript/WebGL lightweight face tracking library designed for augmented reality webcam filters.”

Here’s the dog filter demo I used:

Here’s a link to the code I used:

https://github.com/jeeliz/jeelizFaceFilter/tree/master/demos/threejs/dog_face

 

 

Kyle + Tori | Final Blog

*victorious trumpet sounds* Wow! We made it. Because this was such a massive project, this is going to be a pretty long post, so buckle up and get ready to learn about how we created the Interactive Dartboard/Hangman Game…HangDarts!!!

During our brainstorm session we agreed on a couple of things right away.

  1. We want our finished product to incorporate both physical and digital elements (meaning we would somehow create an input using Arduino, then send it into Processing to make something happen).
  2. We want the interaction to be highly physical and almost visceral in nature. This would serve to draw participants into the interaction.
  3. We wanted to find a way to make it more meaningful to each individual user than something that just displayed shapes on a screen. In other words, we wanted to create some sort of game.

After some deliberation, we decided that the action of a person playing game of darts would be a great way to incorporate a physical interaction. After discussion about how to make a dartboard that was able to create a digital output, we decided that the most “genuine” way to create a dartboard was to incorporate several switches into the board itself. Each panel of the board would need to be covered in a permeable conductive material, so that when a conductive dart pierced it, it could connect the two layers, turning on the switch.  

Having the project roughly planned like this was very encouraging. We seemed to have a really clear idea of what we wanted to do and it seemed like we weren’t taking on too much. Actually, there was no point in the process did we think that we were taking on too much for our given two-week time frame.

The problem with making an interactive dartboard was trying to create something more meaningful than the board itself. We needed to add another layer of meaning to our product. After discussing what we thought the board could actually do, we settled on making each panel of our board represent a different letter, then using those letters to play a game of hangman. At first, we planned on making a panel for each of the 26 letters, but realized that each letter would not be used the same amount. We also wanted to minimize the risk of failure by limiting the number of switches we would need to create. Eventually, he design that we settled on was a 1 meter wide grid split into ten panels. The top five panels would be associated with the letters: a, e, i, o, u, as vowels are the first letters you guess in a game of hangman. On the bottom row, we had sets of several consonants (bcdf, ghjk, lmnp, qrst, and vwxyz) in order to both condense our board, and make the game play slightly different from a traditional game of hangman.

The grouping of the consonants also helped to make the game both shorter and slightly easier for the people playing, considering that one of the hard parts of the game would already be successfully hitting each target.  During user testing at the IM showcase our theory was supported as we found that participants began to use statistics to try to win the game (“If I hit the bcdf panel, I can make three words, and if I hit the lmnp panel I can only make one word so I’m going for the bcdf”). This was really interesting to see, not only because it was unexpected, but it also added an interesting cerebral aspect to the game. It made people think. This also made it fun for others to watch the participant play.

Once we had the design completed, we split up in order to actually make it (even though we worked at the same time in the same room). Tori began to work on coding the actual interface of the game, which would be displayed above the dartboard, while Kyle began working on the physical dartboard itself. Thus fed into our strengths and led to us having a stronger project overall. Because we were working next to each other, we could consult the other if we had a problem. After getting to a point where we could begin to incorporate our two parts together, we began working more closely on each other’s parts.

Our prototype was one of the main aspects of this project that gave us hope to create the full product. After testing out a dozen or so different combinations of materials, we decided that the most durable, reliable, and available solution would be to create the switches by gluing a piece of conductive fabric to each side of a piece of foam board. Each cell is individually connected to a breadboard where it serves as a switch: if there is a dart connecting the two sides, it would read as ON, and it it was removed, it would read as OFF. In addition to the actual switch, We had to design effective housing, to minimize the size and weight of the board, while also supporting the grid structure and effectively separate each switch, so that we wouldn’t get any errors. In addition, we needed to make it sturdy enough to withstand being hit by a stray dart.

In addition to the board prototype, We made a small button prototype with a set of 10 labeled buttons in order to start building the code that would send data over from arduino into the Processing program. Ideally, using this we would have been able to build the code and then just plug the board in once it was finished.

In order to bring our prototype to full scale, Kyle spent a lot of time at the laser cutter (booking 9 cutting sessions in total, because of errors with the cutter, and working around limited acrylic supplies). The base of the board was made out of two large pieces of 6 mm thick acrylic, which were bolted together with foam board and thinner acrylic to provide protection for the panels.

Once everything was cut, We took about two full tables in the IM lab with all of the fabric and foam spread out and just spent an hour or two drawing an outline, cutting, and wiring up all of the panels. Once everything was assembled, we went to activate everything, we found our first mistake: three of the wires connecting the panels to the arduino were too short. Luckily this was a quick fix. We just had to solder a little more wire onto what was already there. Last minute, we also added a small wooden board which we glued the arduino to, and a support arm, to make the entire system more maneuverable. We worked around this well, moving the angle of the arm so that Tori could solder the wires while Kyle worked on hand-drawing the letters onto the panels.

Now, back to the code! This bitch was a whole lot to write (about 300 lines in total, including classes and functions). Luckily though, most of it was logical. The basics of what we needed was: Recognize when the switch is turned on, check to see if the randomized word contains that letter. If it does, write the letter, if it doesn’t, draw the stick figure. Then there would be a reset button that clears the drawn stick figure and re-randomizes the word.

Up until then, Tori had handled the code rather well. However, there were a few things that just wouldn’t work. Luckily, Jack helped Tori figure that out. One such was the communication between Arduino and Processing.

Tori’s Comments on writing the code:

However, for the life of me, I could not figure out how to get my processing sketch to communicate with the arduino. And then Jack came in and fixed it. My issue? In a moment of complete student stress and exhaustion, we had began counting with 1 instead of 0. Cue an outraged cry of “THAT WAS IT???” as Jack pointed it out, we changed it, and it instantly worked. So. Frustrating.

He also helped me at the very end when I couldn’t figure out how to put in a reset button, which was another very easy fix. “Tori you’re calling this class to do all of the stuff. Just call the class again every time you push the button”. “Oooooohhhh”. Overall, Jack is a lifesaver and I thank him heavily for his help with this project.

At this point, I’m going to put the code.

Processing:

import processing.serial.*;    
Serial myPort;

int buttonA;
int buttonE;
int buttonI;
int buttonO;
int buttonU;
int buttonBCDF;
int buttonGHJK;
int buttonLMNP;
int buttonQRST;
int buttonVWXYZ;

//int numWords = 17;
//int number = int(random(0, numWords)); //to add the randomize word right into the reset button. 

Text text = new Text();

PFont f;
PImage paper;    //background for hangman
PImage pin;      //detail
PImage hangman;  //hangman drawing
PImage sticky;    //aesthetic sticky notes
PImage cork;     //cork background
PImage dart;    // for animation of the wrong letters
PImage sadBoi; //sadBoi hours
PImage torn; //gameover 

boolean reset = false;  //for reset button

int values[]= new int[10];

void setup() {
  fullScreen();
  f = createFont("Comic Sans MS Bold Italic", 50);

  printArray(Serial.list());          //setup for send to Ardy
  String portname=Serial.list()[0];
  println(portname);
  myPort = new Serial(this, portname, 9600);
  myPort.clear();
  myPort.bufferUntil('\n');

  cork = loadImage("cork.jpg");
  paper = loadImage("paper.png");
  pin = loadImage("pin.png");
  hangman = loadImage("hangman.gif");
  sticky = loadImage("sticky.png");

  textFont(f);
}

void draw() {
  if (reset == true) {  //setting the fail and pass counters back to 0 for reset button
    image(cork, 0, 0, width, height);    //draw the basic background
    image(paper, 0, 0, 1000, 1000);
    image(pin, 440, 100, 80, 70);
    image(hangman, 170, 200, 600, 600);
    image(sticky, 1300, 70, 600, 400);

    text.display();
    //text.fail = 0;
    //text.pass = 0;

    alphabet();
    text.run(values);
  }    //connected to line 64
}

void serialEvent(Serial myPort) {      //send to Ardy

  String s=myPort.readStringUntil('\n');
  s=trim(s);
  if (s!=null) {
    //println(s);    //to check the connections
    values = int (split(s, ','));    //I don't think we need this but whatever?
    if (values.length == 10) {
      buttonA = values[0];
      buttonE = values[1];
      buttonI = values[2];
      buttonO = values[3];
      buttonU = values[4];
      buttonBCDF = values[5];
      buttonGHJK = values[6];
      buttonLMNP = values[7];
      buttonQRST = values[8];
      buttonVWXYZ = values[9];
    }
  }

  myPort.write(buttonA);
}

void keyPressed() {   //assign the reset button to the space bar
  if (key == ' ') {
    reset = true;
     text = new Text();
  }
}
class Text {
  boolean textAppearA = false;    //so many booleans. there's gotta be a better way to do this. 
  boolean textAppearE = false;
  boolean textAppearI = false;
  boolean textAppearO = false;
  boolean textAppearU = false;
  boolean textAppearB = false;
  boolean textAppearG = false;
  boolean textAppearL = false;
  boolean textAppearQ = false;
  boolean textAppearV = false;

  int fail = 0;    //for drawing the stick figure based on errors
  int pass = 0;    //for "You win" 

  int numWords = 54;  //how many words in the doc, could use message.length but I don't feel like it. 
  int number = floor(random(0, numWords));    //randomize the words

  Text() {
  }

  void display() {
    for (int i = 0; i < 4; i++) {
      image(sticky, 850+(250*i), 600, 250, 250);
    }
  }

  void run(int[] _values) {
    sadBoi = loadImage("sad.png");
    dart = loadImage("dart.png"); 
    torn = loadImage("torn.png");

    String[] message = loadStrings("words.txt");
    fill(0);  //setting the font color to black


    if (_values[0] == 1) {
      image(dart, 1255, 30, 150, 150);
      if (textAppearA == false) {

        if ((message[number].charAt(0) != 'a') && (message[number].charAt(1) != 'a') && (message[number].charAt(2) != 'a') && (message[number].charAt(3) != 'a')) {
          fail++;
        }
        if ((message[number].charAt(0) == 'a') || (message[number].charAt(1) == 'a') || (message[number].charAt(2) == 'a') || (message[number].charAt(3) == 'a')) {
          pass++;
        }
        textAppearA = true;
      }
    }

    if (textAppearA == true) {
      for (int s = 0; s < 4; s++) {
        if (message[number].charAt(s) == 'a') {
          text(message[number].charAt(s), 955 + (250*s), 730);
        }
      }
    }

    if (_values[1] == 1) {
      image(dart, 1355, 30, 150, 150);

      if (textAppearE == false) {      //NOTE: MAKE ALL OF THIS INTO A FUNCTION SO THE CODE ISN'T AS LONG?


        if ((message[number].charAt(0) != 'e') && (message[number].charAt(1) != 'e') && (message[number].charAt(2) != 'e') && (message[number].charAt(3) != 'e')) {
          fail++;
        }
        if ((message[number].charAt(0) == 'e') || (message[number].charAt(1) == 'e') || (message[number].charAt(2) == 'e') || (message[number].charAt(3) == 'e')) {
          pass++;
        }

        textAppearE = true;
      }
    }

    if (textAppearE==true) {
      for (int s = 0; s < 4; s++) {
        if (message[number].charAt(s) == 'e') {
          text(message[number].charAt(s), 955 + (250*s), 730);
        }
      }
    }
    if ( _values[2] ==1) {
      image(dart, 1455, 30, 150, 150);

      if (textAppearI == false) {  

        if ((message[number].charAt(0) != 'i') && (message[number].charAt(1) != 'i') && (message[number].charAt(2) != 'i') && (message[number].charAt(3) != 'i')) {
          fail++;
        }
        if ((message[number].charAt(0) == 'i') && (message[number].charAt(1) == 'i') || (message[number].charAt(2) == 'i') || (message[number].charAt(3) == 'i')) {
          pass++;
        }

        textAppearI = true;
      }
    }

    if (textAppearI==true) {
      for (int s = 0; s < 4; s++) {
        if (message[number].charAt(s) == 'i') {
          text(message[number].charAt(s), 955 + (250*s), 730);
        }
      }
    }
    if (_values[3] == 1) {
      image(dart, 1555, 30, 150, 150);
      if (textAppearO == false) {     

        if ((message[number].charAt(0) != 'o') && (message[number].charAt(1) != 'o') && (message[number].charAt(2) != 'o') && (message[number].charAt(3) != 'o')) {
          fail++;
        }
        if ((message[number].charAt(0) == 'o') || (message[number].charAt(1) == 'o') || (message[number].charAt(2) == 'o') || (message[number].charAt(3) == 'o')) {
          pass++;
        }
        textAppearO = true;
      }
    }

    if (textAppearO==true) {
      for (int s = 0; s < 4; s++) {
        if (message[number].charAt(s) == 'o') {
          text(message[number].charAt(s), 955 + (250*s), 730);
        }
      }
    }

    if  (_values[4] == 1) {
      image(dart, 1655, 30, 150, 150);
      if (textAppearU == false) {  

        if ((message[number].charAt(0) != 'u') && (message[number].charAt(1) != 'u') && (message[number].charAt(2) != 'u') && (message[number].charAt(3) != 'u')) {
          fail++;
        }
        if ((message[number].charAt(0) == 'u') || (message[number].charAt(1) == 'u') || (message[number].charAt(2) == 'u') || (message[number].charAt(3) == 'u')) {
          pass++;
        }
        textAppearU = true;
      }
    }

    if (textAppearU ==true) {
      for (int s = 0; s < 4; s++) {
        if (message[number].charAt(s) == 'u') {
          text(message[number].charAt(s), 955 + (250*s), 730);
        }
      }
    }
    if (_values[5] == 1) {
      image(dart, 1335, 125, 150, 150);

      if (textAppearB == false) { 

        if ((message[number].charAt(0) != 'b') && (message[number].charAt(1) != 'b') && (message[number].charAt(2) != 'b') && (message[number].charAt(3) != 'b')
          && (message[number].charAt(0) != 'c') && (message[number].charAt(1) != 'c') && (message[number].charAt(2) != 'c') && (message[number].charAt(3) != 'c')
          && (message[number].charAt(0) != 'd') && (message[number].charAt(1) != 'd') && (message[number].charAt(2) != 'd') && (message[number].charAt(3) != 'd')
          && (message[number].charAt(0) != 'f') && (message[number].charAt(1) != 'f') && (message[number].charAt(2) != 'f') && (message[number].charAt(3) != 'f')) {
          fail++;
        }

        if ((message[number].charAt(0) == 'b') || (message[number].charAt(1) == 'b') || (message[number].charAt(2) == 'b') || (message[number].charAt(3) == 'b')
          || (message[number].charAt(0) == 'c') || (message[number].charAt(1) == 'c') || (message[number].charAt(2) == 'c') || (message[number].charAt(3) == 'c')
          || (message[number].charAt(0) == 'd') || (message[number].charAt(1) == 'd') || (message[number].charAt(2) == 'd') || (message[number].charAt(3) == 'd')
          || (message[number].charAt(0) == 'f') || (message[number].charAt(1) == 'f') || (message[number].charAt(2) == 'f') || (message[number].charAt(3) == 'f')) {
          pass++;
        }

        textAppearB = true;
      }
    }

    if (textAppearB == true) {
      for (int s = 0; s < 4; s++) {
        if ((message[number].charAt(s) == 'b') || (message[number].charAt(s) == 'c') || (message[number].charAt(s) == 'd') || (message[number].charAt(s) == 'f'))
          text(message[number].charAt(s), 955 + (250*s), 730);
      }
    }
    if (_values[6] == 1) {
      image(dart, 1555, 125, 150, 150);
      if (textAppearG == false) {  

        if ((message[number].charAt(0) != 'g') && (message[number].charAt(1) != 'j') && (message[number].charAt(2) != 'j') && (message[number].charAt(3) != 'j')
          && (message[number].charAt(0) != 'h') && (message[number].charAt(1) != 'h') && (message[number].charAt(2) != 'h') && (message[number].charAt(3) != 'h')
          && (message[number].charAt(0) != 'j') && (message[number].charAt(1) != 'j') && (message[number].charAt(2) != 'j') && (message[number].charAt(3) != 'j')
          && (message[number].charAt(0) != 'k') && (message[number].charAt(1) != 'k') && (message[number].charAt(2) != 'k') && (message[number].charAt(3) != 'k')) {
          fail++;
        }

        if ((message[number].charAt(0) == 'g') || (message[number].charAt(1) == 'j') || (message[number].charAt(2) == 'j') || (message[number].charAt(3) == 'j')
          || (message[number].charAt(0) == 'h') || (message[number].charAt(1) == 'h') || (message[number].charAt(2) == 'h') || (message[number].charAt(3) == 'h')
          || (message[number].charAt(0) == 'j') || (message[number].charAt(1) == 'j') || (message[number].charAt(2) == 'j') || (message[number].charAt(3) == 'j')
          || (message[number].charAt(0) == 'k') || (message[number].charAt(1) == 'k') || (message[number].charAt(2) == 'k') || (message[number].charAt(3) == 'k')) {
          pass++;
        }
        textAppearG = true;
      }
    }
    if (textAppearG == true) {
      for (int s = 0; s < 4; s++) {
        if ((message[number].charAt(s) == 'g') || (message[number].charAt(s) == 'h') || (message[number].charAt(s) == 'j') || (message[number].charAt(s) == 'k'))
          text(message[number].charAt(s), 955 + (250*s), 730);
      }
    }

    if ( _values[7] == 1) {
      image(dart, 1435, 180, 150, 150);
      if (textAppearL == false) {  


        if ((message[number].charAt(0) != 'l') && (message[number].charAt(1) != 'l') && (message[number].charAt(2) != 'l') && (message[number].charAt(3) != 'l')
          && (message[number].charAt(0) != 'm') && (message[number].charAt(1) != 'm') && (message[number].charAt(2) != 'm') && (message[number].charAt(3) != 'm')
          && (message[number].charAt(0) != 'n') && (message[number].charAt(1) != 'n') && (message[number].charAt(2) != 'n') && (message[number].charAt(3) != 'n')
          && (message[number].charAt(0) != 'p') && (message[number].charAt(1) != 'p') && (message[number].charAt(2) != 'p') && (message[number].charAt(3) != 'p')) {
          fail++;
        }

        if ((message[number].charAt(0) == 'l') || (message[number].charAt(1) == 'l') || (message[number].charAt(2) == 'l') || (message[number].charAt(3) == 'l')
          || (message[number].charAt(0) == 'm') || (message[number].charAt(1) == 'm') || (message[number].charAt(2) == 'm') || (message[number].charAt(3) == 'm')
          || (message[number].charAt(0) == 'n') || (message[number].charAt(1) == 'n') || (message[number].charAt(2) == 'n') || (message[number].charAt(3) == 'n')
          || (message[number].charAt(0) == 'p') || (message[number].charAt(1) == 'p') || (message[number].charAt(2) == 'p') || (message[number].charAt(3) == 'p')) {
          pass++;
        }

        textAppearL = true;
      }
    }

    if (textAppearL == true) {
      for (int s = 0; s < 4; s++) {
        if ((message[number].charAt(s) == 'l') || (message[number].charAt(s) == 'm') || (message[number].charAt(s) == 'n') || (message[number].charAt(s) == 'p'))
          text(message[number].charAt(s), 955 + (250*s), 730);
      }
    }

    if (_values[8] == 1) {
      image(dart, 1335, 250, 150, 150);
      if (textAppearQ == false) {  


        if ((message[number].charAt(0) != 'q') && (message[number].charAt(1) != 'q') && (message[number].charAt(2) != 'q') && (message[number].charAt(3) != 'q')
          && (message[number].charAt(0) != 'r') && (message[number].charAt(1) != 'r') && (message[number].charAt(2) != 'r') && (message[number].charAt(3) != 'r')
          && (message[number].charAt(0) != 's') && (message[number].charAt(1) != 's') && (message[number].charAt(2) != 's') && (message[number].charAt(3) != 's')
          && (message[number].charAt(0) != 't') && (message[number].charAt(1) != 't') && (message[number].charAt(2) != 't') && (message[number].charAt(3) != 't')) {
          fail++;
        }
        if ((message[number].charAt(0) == 'q') || (message[number].charAt(1) == 'q') || (message[number].charAt(2) == 'q') || (message[number].charAt(3) == 'q')
          || (message[number].charAt(0) == 'r') || (message[number].charAt(1) == 'r') || (message[number].charAt(2) == 'r') || (message[number].charAt(3) == 'r')
          || (message[number].charAt(0) == 's') || (message[number].charAt(1) == 's') || (message[number].charAt(2) == 's') || (message[number].charAt(3) == 's')
          || (message[number].charAt(0) == 't') || (message[number].charAt(1) == 't') || (message[number].charAt(2) == 't') || (message[number].charAt(3) == 't')) {
          pass++;
        }
        textAppearQ = true;
      }
    }

    if (textAppearQ == true) {
      for (int s = 0; s < 4; s++) {
        if ((message[number].charAt(s) == 'q') || (message[number].charAt(s) == 'r') || (message[number].charAt(s) == 's') || (message[number].charAt(s) == 't'))
          text(message[number].charAt(s), 955 + (250*s), 730);
      }
    }

    if (_values[9] ==1) {
      image(dart, 1590, 250, 150, 150);

      if (textAppearV == false) {  


        if ((message[number].charAt(0) != 'v') && (message[number].charAt(1) != 'v') && (message[number].charAt(2) != 'v') && (message[number].charAt(3) != 'v')
          && (message[number].charAt(0) != 'w') && (message[number].charAt(1) != 'w') && (message[number].charAt(2) != 'w') && (message[number].charAt(3) != 'w')
          && (message[number].charAt(0) != 'x') && (message[number].charAt(1) != 'x') && (message[number].charAt(2) != 'x') && (message[number].charAt(3) != 'x')
          && (message[number].charAt(0) != 'y') && (message[number].charAt(1) != 'y') && (message[number].charAt(2) != 'y') && (message[number].charAt(3) != 'y')
          && (message[number].charAt(0) != 'z') && (message[number].charAt(1) != 'z') && (message[number].charAt(2) != 'z') && (message[number].charAt(3) != 'z')) {
          fail++;
        }

        if ((message[number].charAt(0) == 'v') || (message[number].charAt(1) == 'v') || (message[number].charAt(2) == 'v') || (message[number].charAt(3) == 'v')
          || (message[number].charAt(0) == 'w') || (message[number].charAt(1) == 'w') || (message[number].charAt(2) == 'w') || (message[number].charAt(3) == 'w')
          || (message[number].charAt(0) == 'x') || (message[number].charAt(1) == 'x') || (message[number].charAt(2) == 'x') || (message[number].charAt(3) == 'x')
          || (message[number].charAt(0) == 'y') || (message[number].charAt(1) == 'y') || (message[number].charAt(2) == 'y') || (message[number].charAt(3) == 'y')
          || (message[number].charAt(0) == 'z') || (message[number].charAt(1) == 'z') || (message[number].charAt(2) == 'z') || (message[number].charAt(3) == 'z')) {
          pass++;
        }
        textAppearV = true;
      }
    }

    if (textAppearV == true) {
      for (int s = 0; s < 4; s++) {
        if ((message[number].charAt(s) == 'v') || (message[number].charAt(s) == 'w') || (message[number].charAt(s) == 'x') || (message[number].charAt(s) == 'y') || (message[number].charAt(s) == 'z')) {
          text(message[number].charAt(s), 955 + (250*s), 730);
        }
      }
    }

    pushStyle();
    noFill();    //make stick figure "transparent"
    strokeWeight(10);
    if (fail >= 1) {
      ellipse(580, 360, 100, 100);
    }

    if (fail >= 2) { 
      line(580, 420, 580, 580);
    }

    if (fail >= 3) { 
      line(580, 580, 550, 640); 
      line(580, 580, 610, 640);
    }
    if (fail >= 4) { 
      line(580, 440, 550, 520); 
      line(580, 440, 610, 520);
    }

    if (fail >= 4) {   //game over
      pushMatrix();
      pushStyle();
      textSize(90); 
      rotate(-.4);
      fill(255, 0, 0);

      image(torn, 175, height/2+100, 556*1.75, 215*1.75);
      text("GAME OVER", 420, 850);
      popStyle();
      popMatrix();
      for (int s = 0; s < 4; s++) {
        text(message[number].charAt(s), 955 + (250*s), 730);
      }
      image(sadBoi, 550, 327, 60, 60);
      //dartSize = 0;
    }

    if (pass >= 4) { 
      pushMatrix();
      pushStyle();
      textSize(90); 
      rotate(-.4);
      fill(0, 150, 0);
      image(torn, 175, height/2+100, 556*1.75, 215*1.75);
      text("YOU WIN!", 420, 850);
      popStyle();
      popMatrix();
    }

    //if (keyPressed && (key == 'k')) {      //work this into the reset button
    //  number = int(random(0, numWords));
    //}
  }
}
void alphabet() {
  pushStyle();
  fill(0);

  text("a", 1385, 175);
  text("e", 1485, 175);
  text("i", 1585, 175);
  text("o", 1685, 175);
  text("u", 1785, 175);

  text("bcdf", 1420, 255);
  text("ghjk", 1640, 255);
  text("lmnp", 1530, 325);
  text("qrst", 1420, 395);
  text("vwxyz", 1640, 395);
  popStyle();
}

Arduino:

int a = 11;     //pin numbers don't worry about this
int e = 2;
int i = 3;
int o = 4;
int u = 5;
int bcdf = 6;
int ghjk = 7;
int lmnp = 8;
int qrst = 9;
int vwxyz = 10;


void setup() {

  Serial.begin(9600);
  //  Serial.println("0,0,0,0");
  establishContact();
}

void loop() {
  if (Serial.available() > 0) {
  int  inByte = Serial.read();

    int aRead = digitalRead(a);
    int eRead = digitalRead(e);
    int iRead = digitalRead(i);
    int oRead = digitalRead(o);
    int uRead = digitalRead(u);
    int bcdfRead = digitalRead(bcdf);
    int ghjkRead = digitalRead(ghjk);
    int lmnpRead = digitalRead(lmnp);
    int qrstRead = digitalRead(qrst);
    int vwxyzRead = digitalRead(vwxyz);

    Serial.print(aRead);
    Serial.print(',');
    delay(1);
    Serial.print(eRead);
    Serial.print(',');
    delay(1);
    Serial.print(iRead);
    Serial.print(',');
    delay(1);
    Serial.print(oRead);
    Serial.print(',');
    delay(1);
    Serial.print(uRead);
    Serial.print(',');
    delay(1);
    Serial.print(bcdfRead);
    Serial.print(',');
    delay(1);
    Serial.print(ghjkRead);
    Serial.print(',');
    delay(1);
    Serial.print(lmnpRead);
    Serial.print(',');
    delay(1);
    Serial.print(qrstRead);
    Serial.print(',');
    delay(1);
    Serial.println(vwxyzRead);
    //  Serial.println(',');
  }
}

void establishContact() {
  while (Serial.available() <= 0) {
    Serial.println("0,0,0,0,0,0,0,0,0,0");   // send an initial string
    delay(300);
  }
}

Now, comes the time for the IM showcase. The big night. We were nervous, honestly. What if something went wrong? What if it didn’t work? What if the people who user tested it were just being nice? We had done user testing and had so much great feedback…oh right…User testing..

Before we had everything totally done, we asked some friends to participate in some User Testing. Here are the videos.

The users gave us a lot of really solid feedback. For example, one user said it would be cool to try and find a way to make the game multiplayer, have one person enter the word and the other person guess it. While we agreed that would be really cool, we thought it might get too crazy and be unfair if people could just add things that weren’t words.

Also, we learned that it is super fun and really satisfying to play. Watching the drawings come up right when the dart goes into the board really signifies that there is an interaction taking place between the user and the game. We received this feedback from multiple people. Every time people saw one person testing it, others would ask to play as well, even when it wasn’t completely finished.

When we moved into our final showcase space, we got some more user testing and feedback. It was suggested that we shine a spotlight on the board so people can see the letters better. we also decided on a set throwing distance as a way of controlling the space more and making it safer to use in the crowded Showcase space. We also did this because many users tried to throw from too close to the board, which would mean their dart wouldn’t have enough momentum to effectively pierce both layers of the board.

Overall, All of our user testers were really encouraging.

Okay back to the IM showcase. We were nervous as hell. But as soon as the show started, and the public began interacting with our game, it was impossible not to become overcome with a sense of pride. Throughout the entire two-hour-long event, there was nearly always a line of people waiting to play, while others gathered around watching. Bystanders would help to guess the word, and often take pictures and videos of the game as it was played. We believe this was the most delightful surprising discovery of the entire experience. Once the room was full, people began interacting with our board in an entirely new way. It was no longer a game of person vs. board, but of everybody trying to guess the word. People would even switch off who threw darts. The game became so multiplayer without us even trying. One person would throw the darts and four others would yell suggestions for letters. Or two friends would come and throw darts together while their other friends looked on. There was never just one person playing this game. It was incredible to watch so many people have fun with something that we made.

Another thing that we didn’t really think about was the replayability of the game. Throughout the showcase, the same people kept coming back to play again and, bringing their friends with them. Each time they played, they still had fun! Even we, the creators remained entertained the entire night, watching people throw darts over and over.

All in all, it was so much fun and so rewarding to see everyone have such a good experience with our project. Thank you for such a great semester!

Here are some pictures and videos of the IM showcase.

Final Project

For my final project, I wanted to create an interactive piece where users can tap El wires that light up and map animations onto the wall depending on where that El wire is installed on the wall. When tapped, a tilt sensor sends a signal to Processing, the sound attached to that wire will play, and the animation for that wire will show up.

I worked with Serial communication between Arduino and Processing in this project and playing with El wires, and projection mapping.

Here is the basic animation:

Here is the prototype:

Problems I ran into:

  1. The main problem I had throughout this project was with the El wires. I spent an entire day or two trying to figure out how to get the el wires wired up so that it would run. At first, I tested it out with just AA batteries and plugging that in with the wires, which worked. I then looked up many videos on how to get the wires to work with Arduino. Many of them told me to hook them up to a relay and other tutorials required Sparkfun sequencers to program the El wires, so I spent a lot of my time trying different methods, but all to no avail. I then found a video on YouTube that went through how to wire up a 12V inverter power, which was very helpful. With that, I was finally able to wire them up correctly.
  2. After wiring them up, I had a problem where one of the lights would not turn on whatsoever, which required me to work on it overnight, but I still couldn’t find out why. After debugging with Aaron, we found out one of the power did not work at all for any of the wires. Luckily I had extra one so that I could still work with three wires.
  3. On the day of the show, another battery was acting up. It kept blinking on and off so when people tapped the orange El wire, the light wouldn’t go off. At this point, I couldn’t change anything, but if I had known the inverters wouldn’t be so reliable, I could have looked for another 12V power source to light the El wires.
  4. I tried to hook up the sound right before the show and for some reason, it wasn’t connecting to my speaker, so there was no sound. I wanted it to play the guitar notes with each wire, but that didn’t happen. There were a few people who suggested me to add some sound to it to make it more interesting and I had to tell them that it was there, but didn’t work right now.

If I had more time:

If I had more time and wire, I would ideally like to have 6 wires displayed and 6 notes playing.

What I would change for next time:

If I had managed my time better, I would ideally have the wires set up on a pole ahead of time (in time for the user testing part of the project), so that I could ask people to test it out. I now see important it is because I didn’t realize that some people might not know what to do with the interactive piece since there are no indicators that it works. The only way to see what the project is doing is by interacting with it, so if no one is touching the El wire, my project is essentially El wires tied onto a pole that does nothing. Next time, I would add some sort of indicator. Maybe when the project is idle, there would be a certain animation, or I could put some text on my Processing sketch to ask the user to play with the wires. I could also make the lights go on and off when no one is interacting with it.

Additionally, I wish I had set up the sound way beforehand to make sure my sound was working. I also wish I had a better projector since it was very light and pixelated.

Overall, I wish I had fixed the small inconsistent bugs and had found a way to get people to come play with the project without me ushering them over since the project looked like nothing if no one interacts with it.

int tiltPin1 = 2;
int tiltPin2 = 3;
int tiltPin3 = 4;
int tiltPin4 = 5;
int tiltPin5 = 6;
int tiltPin6 = 7;

int tiltLED[] = {8, 9, 10, 11, 12, 13};
int tiltLEDState[] = {0, 0, 0, 0, 0, 0};

int interval = 1000;
unsigned long previousMillis = 0;

void setup() {
  Serial.begin(9600);
  pinMode(tiltPin1, INPUT);
  pinMode(tiltPin2, INPUT);
  pinMode(tiltPin3, INPUT);
  pinMode(tiltPin4, INPUT);
  pinMode(tiltPin5, INPUT);
  pinMode(tiltPin6, INPUT);

  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
  pinMode(12, OUTPUT);
  pinMode(13, OUTPUT);
}

void loop() {
  int tiltState1 = digitalRead(tiltPin1);
  int tiltState2 = digitalRead(tiltPin2);
  int tiltState3 = digitalRead(tiltPin3);
  int tiltState4 = digitalRead(tiltPin4);
  int tiltState5 = digitalRead(tiltPin5);
  int tiltState6 = digitalRead(tiltPin6);

  if (tiltState1 == 1) {
    Serial.write("1");
    tiltLEDState[0] = HIGH;
  } else {
    Serial.write("0");
  }
  
  Serial.write(",");

  if (tiltState2 == 1) {
    Serial.write("1");
    tiltLEDState[1] = HIGH;
  } else {
    Serial.write("0");
  }

  Serial.write(",");
  
  if (tiltState3 == 1) {
    Serial.write("1");
    tiltLEDState[2] = HIGH;
  } else {
    Serial.write("0");
  }

  Serial.write(",");

  
  if (tiltState4 == 1) {
    Serial.write("1");
    tiltLEDState[3] = HIGH;
  } else {
    Serial.write("0");
  }

  Serial.write(",");
  
  if (tiltState5 == 1) {
    Serial.write("1");
    tiltLEDState[4] = HIGH;
  } else {
    Serial.write("0");
  }

  Serial.write(",");
  
  if (tiltState6 == 1) {
    Serial.write("1");
    tiltLEDState[5] = HIGH;
  } else {
    Serial.write("0");
  }

  Serial.write("\n");

  checkLEDDelay();
}


void checkLEDDelay() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    for (int i = 0; i < 6; i++) {
      if (tiltLEDState[i] == HIGH) {
        tiltLEDState[i] = LOW;
      }
    }
  }

  for (int i = 0; i < 6; i++) {
    digitalWrite(tiltLED[i], tiltLEDState[i]);
  }
}

Unfortunately, I forgot to record/take a picture of the final piece :/

Final Project Documentation

I am very happy with how the showcase went for what I created! I thought that it was a very interesting user testing session as well. I previously thought that the instructions and flow of the game were intuitive and easily flowed together. However, when people who had zero context of the game or what it was and the physical interface, they sometimes struggled in knowing what to do.

I think that this project really forced me to reflect on what makes an interactive media project, or any entertaining product successful, and I think I realized that people don’t necessarily find the most complex of interactions entertaining, but sometimes these are just simple things with a clear feedback loop. The part of the game that had the most success was probably just people walking up to it and petting the stuffed toy’s head and seeing hearts coming out of the horse or hitting the padded force sensor with a hammer. People understood the deeper meaning of the game that I was attempting to convey, and I got responses like “oh yeah that’s cool,” but they were nowhere near as excited after petting the animal. It now seems so obvious that that interaction would evoke such a strong reaction because of how intuitive and personal it felt.

There were four stages to the game: a forested level, a desert area, an ice area and a wooded area. The idea is that the objective of each other remains the same: the player is able to interact with the animals and harvest resources in order to build a piece of technology. As the player progresses through the game, the animals become increasingly scared of the player and the amount of available resources begins to dwindle, the environment changes and the music becomes more dramatic and tense. The whole idea is that the human’s requests from nature for things to improve their livelihoods are initially meager, like the train and the airplane necessary for movement and communication, but as the human creates these things, their requests become more elaborate and arguably more unnecessary.

All of the assets that I took for the game were from: itch.io and some of the other assets were taken from Minecraft.

I also spent a lot of time on the sound and music used during the game and since this style of the character and assets was reminiscent of older 2D games like Pokemon, I used music from Harvest Moon and Pokemon, as well as sound effects from Minecraft.

Here is a link to the github repository with all of the assets and code:

https://github.com/slw515/introToIM/tree/master/finalIntro%20copy%203/finalIntro

A few pictures and a video of my project’s setup and someone playing it during the showcase:

It was super awesome to see this all come to life even though it did not turn out like how I envisioned it! Thank you to Aaron and everyone who helped, supported or just simply saw my project, it has been an awesome semester!

Nick and Paulin: Iron Man Shooting Game Full Documentation

So here it is. The end of our project. I honestly do not know where to begin. I guess it started as just a small idea Paulin had, and after discussing it with me our excited minds started to race towards building the game.

When we started planning the blueprint of our game, we knew that our common goal was to make the game as authentic as possible. We really desired the user to have an experience with their body as if they were Iron Man, which is why we had the kinetic camera idea along with the Iron Man glove. Moreover, we also needed the game to be like a “video game”. Not so simple, and not so easy. With that in mind, here is what we have accomplished.

Like most video games, we had a main menu and features inside it.

The menu would have buttons so that each would serve its function: play game, take a look at scoreboard, read the instructions, quit. In case you are wondering about the black screen in the middle, we decided that it would be better to have a demo before first-timers started playing the game. So we implemented a visualization of the camera so that the target could see themselves or specifically their kinetic movements to know that their aiming was supposed to be colored red, and if they pushed their arm forward (color changed to dark red) they knew they were shooting.

Inside the game, there are mainly three features. On the upper left hand side, you can see that the timer is ticking down. We set this to 2 minutes so that everyone during the showcase could have a fair chance and amount of time to play the game. On the upper right hand side, you could see a counter which measured the score. This counted the targets we killed and stored them into the array, then displaying the size of that array. Since the draw() function was looping, this was updating the whole time. On the bottom right hand side, we again had the visualization of the kinetic camera to show how the user was moving in relation to the camera’s view.

We then added targets. These would appear randomly and move in random animations as long as they were inside the screen. When the user aimed and the shooting motion was detected (the shoot was defined as object closest to kinetic camera, which was arm pushed forward) the targets would be hit and disappear. We had a cursor as well to update where the user was moving . The animations at some point were moving very smoothly, but when we tried to add another png image (Thanos) as another target to shoot, things had a bad turn and the whole game slowed down quite intensively. We managed to make it faster by cutting parts that we didn’t need, save as much CPU and memory as possible, but I believe that after a while Paulin’s Macbook Air could not handle an intensive game like this very well. That was certainly one aspect that felt really sore, but in the end it was all the effort worth it to be able to make this sort of game.

After the game, the user would enter their name and their score would appear (from the size of the score array mentioned earlier). Then both the key and value would go into the hash map, the value (score) would be sorted through another array list, and looping through the hash map we would find that score and print out the top five winners. We had to use an array list since hash map did not allow sorting, and hash map was the one of the few data structures that allowed a key:value system that would link two identities of variables together. Either way, numerous people were excited to see their scores being put up in the scoreboard after playing the game.

One of our good friends, Harper, filmed a short clip of our other good friend, Ramon, playing the game.

An earlier version of the game is hard to find since we have been building upon the same file and overwriting the progress as we went. However I do have some earlier code for my scoreboard and my overall menu that I would like to share.

PImage menupic;
PImage bpic;
PImage spic;
PImage ipic;
import controlP5.*;
import java.util.Map;
import java.util.Collections;
int state = 0;
ControlP5 a;
ControlP5 c;
ControlP5 d;
String output;
PFont b;
PFont yo; //for Scoreboard text input
final int menu = 0;
final int game = 1;
final int scoreboard = 2;
final int options = 3;
final int quit = 4;
float score = 10;
int iu = 0;
HashMap<String,Float> hm = new HashMap<String,Float>();
ArrayList<Float> al = new ArrayList<Float>();
int tracker = 0;
int counter = 0;



void setup(){
  menupic = loadImage("menu.jpg"); //menu picture
  bpic = loadImage("background.jpg"); //background picture
  spic = loadImage("scoreboard.jpg"); //scoreboard picture
  ipic = loadImage("instructions.png");
  size(1920,1030);
  
  
  
  
  
  
  //MENU INTERACTIONS
  a = new ControlP5(this); //a is for menu
  b = createFont("Verdana",30); //font for menu
  yo = createFont("Verdana",15); //font for scoreboard form
  a.addButton("Play") //name
    .setPosition(100,150) //position
    .setSize(250,200) //size
    .setFont(b) //font
    ;
  a.addButton("Scoreboard")
    .setPosition(1500,150)
    .setSize(250,200)
    .setFont(b)
    ;
  a.addButton("Instructions")
    .setPosition(100,750)
    .setSize(250,200)
    .setFont(b)
    ;
  a.addButton("Quit")
    .setPosition(1500,750)
    .setSize(250,200)
    .setFont(b)
    ;
    //C = Back Button
  c= new ControlP5(this); //c is for back button
  c.addButton("Back")
    .setPosition(1500,750)
    .setSize(250,200)
    .setFont(b)
    ;
    //D = Scoreboard
  d= new ControlP5(this); //d is for scoreboard screen
  
  d.addTextfield("Insert Name Here").setPosition(200,800).setSize(200,50).setAutoClear(false).setFont(yo);
  d.addBang("Submit").setPosition(400,800).setSize(200,50).setFont(yo);
  
    
}

void draw(){
  image(menupic,0,0);
  if(state == 0){ //Menu
    textSize(40);
    text("MENU",50,100);
    c.hide();
    a.show();
    d.hide();
  }
  else if(state == 1){ //Game
    runGame();
    score = random(50);
    
    a.hide();
    //c.hide();
    c.hide();
    d.hide();
  }
  else if(state == 2){ //Scoreboard
    image(spic,0,0);
    
    c.show();
    a.hide();
    d.show();
    
    updateBoard();
    
  }
  else if(state ==3){ //Instructions
    background(0);
    image(ipic,850,20);
    textSize(40);
    text("1. Click \"Play Game\"",300,400);
    text("2. Stand on the designated platform",300,500);
    text("3. Use your hand to aim, push your hand towards the kinect camera to shoot",300,600);
    text("4. After the game write your name, submit, and see how well you did!",300,700);
    c.show();
    a.hide();
    d.hide();
  }
  else if(state ==4){ //Quit
    exit(); 
  }
}

void runGame(){
  image(bpic,0,0);
}

void Play(){
  state = 1;
}
void Scoreboard(){
  state = 2;
}
void Back(){
  state = 0;
}
void Instructions(){
  state = 3;
}

void Quit(){
  state = 4;
}

void Submit(){ //Submit form for text input in Scoreboard
  output = d.get(Textfield.class,"Insert Name Here").getText();
  
  hm.put(output,score);
  hm.put("hey",10.0);
  al.add(10.0);
  hm.put("hi",7.5);
  al.add(7.5);
  if(score != 0){
    al.add(score);
  }
  
  //println(output);
}

void updateBoard(){
  textSize(60);
  text("SCOREBOARD",750,90);
  
  
  java.util.Collections.sort(al,Collections.reverseOrder());
   
  
    for(int i = 0; i < al.size(); i++){
     //println((float)al.get(i));
     if((float)al.get(i) != 0 && i<5){
         println((float)al.get(i));
        text((float)al.get(i),800, 300+i*60);
        
      }
    }
    


      
    for (Map.Entry me : hm.entrySet()) {
      
      Object test = me.getKey();
      
      if(test != null){
        if(al.contains(me.getValue())){
          if(tracker<5){
             text((String)me.getKey(),400,300+iu*60);
             
             iu+= 1;
             tracker+=1;
          }
        }
        else{
          continue;
        }
      }
    }
    iu = 0;
    tracker = 0;
}

 

Overall, this was very stressful at times but in the end one of the most fun projects I have worked on in my life. Building towards something that I knew other people would marvel at (did you get the pun) and enjoy playing was the true motivation Paulin and I had when we started creating this project. Although the game was lagging a bit during the game, we are still happy that people at the showcase were able to have a fun experience playing our shooting game. I hope you enjoyed reading this as well, and below is the code (main function) of our project. Farewell!

// ======== Final Interactive Media Project Spring 2019 ========= //


//----------Libraries------------------//
import processing.video.*;
import processing.sound.*;
import org.openkinect.freenect.*;
import org.openkinect.freenect2.*;
import org.openkinect.processing.*;
import org.openkinect.tests.*;
import controlP5.*;
import java.util.Map;
import java.util.Collections;

//-------- GLobal Variables----------//

PImage menupic;
PImage bpic;
PImage spic;
PImage ipic;
int state = 0;
ControlP5 a;
ControlP5 c;
ControlP5 d;
String output;
PFont b;
PFont yo; //for Scoreboard text input
final int menu = 0;
final int game = 1;
final int scoreboard = 2;
final int options = 3;
final int quit = 4;
//float score;
int iu = 0;
HashMap<String, Float> hm = new HashMap<String, Float>();
ArrayList<Float> al = new ArrayList<Float>();
int tracker = 0;
int counter = 0;


//Paulin Global Variables
//kinect
PImage img, dImg;
float angle;
Kinect kinect;
boolean shoot;
float minShoot;
float minThresh;
float maxThresh;
//Targets
ArrayList<Targets> targets;
Targets target;
//cursor
Aiming_Box aiming_Circle;
float rx, ry, scale, _rx, _ry;
float _x = 0;
float _y = 0;
//timer
int m, sec;
//score
ArrayList<Integer> score;
//Animations and sound
SoundFile blasterSound;
SoundFile avengers;
Boom boom;
Blaster blaster;
//Movie Intro;
//diframes
ArrayList<Integer> dists;
int numfc;
int threshold;
int randint, pattern;
//Timer
int begin, duration, time;
float prevWidth, prevHeight;
int shootthreshold;
Timer clock;
PImage Imheart;


//-------------------------------------------//
//-------------------------------------------//
//-------------------------------------------//
void setup() {
  //size(640, 480);
  fullScreen();
  menupic = loadImage("menu.jpg"); //menu picture
  bpic = loadImage("background.jpg"); //background picture
  spic = loadImage("scoreboard.jpg"); //scoreboard picture
  ipic = loadImage("instructions.png");

  //score = random(50);


  //Paulin's SetUP
  prevWidth = 1920;
  prevHeight = 1030;
  //Kinect
  kinect = new Kinect(this);
  kinect. initDepth();
  kinect.initVideo();
  img = createImage(kinect.width, kinect.height, RGB); 
  angle = kinect.getTilt();
  shoot = false;
  minShoot = 650;
  minThresh = 700;
  maxThresh = 780;
  //Objects
  targets = new ArrayList<Targets>();
  aiming_Circle = new Aiming_Box(); 
  //Score
  score = new ArrayList<Integer>();
  //Animation
  blasterSound = new SoundFile(this, "shortblast.wav");
  avengers = new SoundFile(this, "IMbgmus.wav");
  avengers.play();
  avengers.loop();
  boom = new Boom();
  // Intro = new Movie(this,"IMintro.mov");
  blaster = new Blaster();
  //DiffFrame
  numfc=10;
  dists = new ArrayList<Integer>(numfc);
  threshold = 40;
  scale = .1;
  //timer
  begin = millis();
  duration = 120;
  time = 180;
  _rx =_ry = 0;
  shootthreshold = 3840;
  clock = new Timer();
  //Imheart = loadImage("ironmanheart.png");

  //------------------------------------//
  //

  //MENU INTERACTIONS
  a = new ControlP5(this); //a is for menu
  b = createFont("Verdana", height*(25/prevHeight)); //font for menu
  yo = createFont("Verdana", height*(15/prevHeight)); //font for scoreboard form
  a.addButton("Play") //name
    .setPosition(width*(100/prevWidth), height*(150/prevHeight)) //position
    .setSize(int(width*(250/prevWidth)), int(height*(200/prevHeight))) //size
    .setFont(b) //font
    ;
  a.addButton("Scoreboard")
    .setPosition(width*(1500/prevWidth), height*(150/prevHeight))
    .setSize(int(width*(250/prevWidth)), int(height*(200/prevHeight)))
    .setFont(b)
    ;
  a.addButton("Instructions")
    .setPosition(width*(100/prevWidth), height*(750/prevHeight))
    .setSize(int(width*(250/prevWidth)), int(height*(200/prevHeight)))
    .setFont(b)
    ;
  a.addButton("Quit")
    .setPosition(width*(1500/prevWidth), height*(750/prevHeight))
    .setSize(int(width*(250/prevWidth)), int(height*(200/prevHeight)))
    .setFont(b)
    ;
  //C = Back Button
  c= new ControlP5(this); //c is for back button
  c.addButton("Back")
    .setPosition(width*(1500/prevWidth), height*(750/prevHeight))
    .setSize(int(width*(250/prevWidth)), int(height*(200/prevHeight)))
    .setFont(b)
    ;
  //D = Scoreboard
  d= new ControlP5(this); //d is for scoreboard screen

  d.addTextfield("Insert Name Here").setPosition(200, 800).setSize(200, 50).setAutoClear(false).setFont(yo);
  d.addBang("Submit").setPosition(400, 800).setSize(200, 50).setFont(yo);
}

void draw() {
  image(menupic, 0, 0);
  if (state == 0) { //Menu
    textSize(30);
    text("MENU", width*(150/prevWidth), height*(150/prevHeight));
    c.hide();
    a.show();
    d.hide();
    img.loadPixels(); 
   SetThresholdsandRecords();// This function goes pixel by pixel looking for the shortest distance to the sensor and will provide the values of rx and ry which will be send to the aiming box
   
   img.updatePixels(); 
   image(img,width/2-width/8 ,height/2-height/8, width/4,height/4);
    time = duration;
    begin = millis();
    for(int i=0; i<score.size();i++){
      score.remove(i);
    }
  } else if (state == 1) { //Game
    runGame();
    a.hide();
    //c.hide();
    c.hide();
    d.hide();
  } else if (state == 2) { //Scoreboard
    image(spic, 0, 0);

    c.show();
    a.hide();
    d.show();

    updateBoard();
  } else if (state ==3) { //Instructions
    background(0);
    image(ipic, 850, 20);
    textSize(40);
    text("1. Click \"Play Game\"", width*(300/prevWidth), height*(400/prevHeight));
    text("2. Stand on the designated platform", width*(300/prevWidth), height*(500/prevHeight));
    text("3. Use your hand to aim, push your hand towards the kinect camera to shoot", width*(300/prevWidth), height*(600/prevHeight));
    text("4. After the game write your name, submit, and see how well you did!", width*(300/prevHeight), height*(700/prevHeight));
    c.show();
    a.hide();
    d.hide();
  } else if (state ==4) { //Quit
    exit();
  }
}


//---------------------------Functions---------------------------------//
void runGame() {
  image(bpic, 0, 0,width,height);
  //image(Intro,0,0,width,height);
  img.loadPixels(); 
  //kinect
  SetThresholdsandRecords();// This function goes pixel by pixel looking for the shortest distance to the sensor and will provide the values of rx and ry which will be send to the aiming box
  img.updatePixels(); 
  image(img,width-width/8 - width/80,height-height/8- height/80, width/8,height/8);
  if (shoot==true) {
    pushStyle();
    fill(255, 0, 0);
    popStyle();
  }
  //Targets
  add_target();
  target_functions(); 
  //Aiming_box
  aiming_Circle.update(rx, ry, shoot);
  color TempColor = color_Aiming_Box();
  aiming_Circle.display(TempColor, shoot, blasterSound, blaster);
  //timer & Score
  timer();
  printScore();
}



void Play() {
  state = 1;
}
void Scoreboard() {
  state = 2;
}
void Back() {
  state = 0;
}
void Instructions() {
  state = 3;
}

void Quit() {
  state = 4;
}

void Submit() { //Submit form for text input in Scoreboard
  output = d.get(Textfield.class, "Insert Name Here").getText();
  //println(output);
  
    if((float)score.size() != 0.0){
      hm.put(output, (float)score.size());
      al.add((float)score.size());
    }
    
  
}

void updateBoard() {
  textSize(60);
  text("SCOREBOARD", 750, 90);


  java.util.Collections.sort(al, Collections.reverseOrder());


  for (int i = 0; i < al.size(); i++) {
    //println((float)al.get(i));
    if ((float)al.get(i) != 0 && i<5) {

      text((float)al.get(i), 1000, 300+i*60);
      println(al.get(i));
    }
  }




  for (Map.Entry me : hm.entrySet()) {

    Object test = me.getKey();

    if (test != null) {
      if (al.contains(me.getValue())) {
        //if (tracker<5) {
          text((String)test, 700, 300+iu*60);
          //println(me.getValue());
          iu+= 1;
          tracker+=1;
        //}
      } else {
        continue;
      }
    }
  }
  iu = 0;
  tracker = 0;
}

/*
      if(al.contains(me.getValue())){
 println(me.getValue());
 if(hm.containsKey(me.getKey())){
 if(me.getValue() != null){
 text((String)me.getKey(),700,500-iu*100);
 text((float)me.getValue(),1000, 500-iu*100);
 iu+= 1;
 }
 
 }
 }
 }
 iu = 0;
 */

//----Paulin's Function----// 
void SetThresholdsandRecords() {
  PImage dImg = kinect.getDepthImage(); //To get the default kinect image to find manually the thresholds
  //image(dImg,0,0); 
  shoot = false;
  int record = 4500;
  _x=_y=0;
  int totdis;
  float total =0;
  int[] depth = kinect.getRawDepth();
  for (int x = 0; x<kinect.width; x++) {
    for (int y =0; y< kinect.height; y++) {
      int offset = x + y*kinect.width;
      int d = depth[offset];
      if (d>=minThresh && d<maxThresh) {
        img.pixels[offset] = color(245,72,72);
        //if (d<record) {
          //record = d;
         _x+=x;
         _y+=y;
        //}
        //_x+=x;
        //_y+=y;
        total++;
      } else  if (d>=minShoot && d<=minThresh){
        img.pixels[offset] = color(255,0,0) ;
        shoot=true;
        _x+=x;
        _y+=y;
        total++;
      }
      else{
       img.pixels[offset] = color(0,0,0) ; 
      }
    }
  }

  if (total>0) {
    _x= _x/total;
    _y= _y/total;
  }
 

  _rx += ((640-_x)-_rx)*scale;
  rx = map(_rx, 150, 640-150, 0, width);
  _ry += (_y-_ry)*scale;
  ry = map(_ry, 110, 480-110, 0, height);
  //ellipse(rx,ry,30,30);
  //dists.add(record);
  //if (dists.size()==numfc)
  //  dists.remove(0);
  //totdis = dists.get(0) - dists.get(dists.size()-1);
  ////println("totdis -> ",totdis);
  //println(totdis);
  //if (totdis>shootthreshold) {
  //  shoot =true;
  //}
  //println("shoot ->", shoot);
}

//To provide a different color to the aiming box if it is shooting
color color_Aiming_Box() {
  color Color;
  Color = color(255, 255, 255);
  for (int i = 0; i<targets.size(); i++) {
    if (aiming_Circle.rx>targets.get(i).x
      &&aiming_Circle.rx<(targets.get(i).x+targets.get(i).w)
      &&aiming_Circle.ry>targets.get(i).y
      &&aiming_Circle.ry<(targets.get(i).y+targets.get(i).h)) {
      Color = color(255, 0, 0);
      break;
    }
  }
  return(Color);
}

//To move the angle of the kinect
void keyPressed() {
  if (key == CODED) {
    if (keyCode == UP) {
      angle++;
    } else if (keyCode == DOWN) {
      angle--;
    }
    angle = constrain(angle, 0, 30);
    kinect.setTilt(angle);
  }
}    

//Set up timer in the right corner
void timer() {
  float lettersize = height*.054;
  float timerposition = width*.04125;
  float Yposition = height*.08125;
  float position = timerposition + (width*.125);
  //float secondposition = position + (width*.022);
  if (time > 0) {  
    time = duration - (millis() - begin)/1000;
    pushStyle(); 
    stroke(188, 23, 23);
    strokeWeight(width*.003);
    fill(188, 166, 23, 100);
    ellipse(timerposition+width*.01, Yposition-lettersize/3.1+height*.01, height*.128, height*.128);
    fill(255);
    textSize(lettersize);
    text(time, timerposition-width*.02, Yposition+height*.01);
    popStyle();
  }
  if (time ==0) {
    state = 2;
    textSize(40);
    text("GAME OVER!: ", 600, 90);
  }
} 

// add a target  every 120 frames
void add_target() { 
  randint = int(random(3));
  pattern = int(random(45));
  if(frameCount%10==0){
    targets.add(new Targets(shoot, randint, pattern));
  }
  //if(time<60 && time>30){
  //if (frameCount%80==0) {
  //  targets.add(new Targets(shoot, randint, pattern));
  //}
  //}
  //else if (time>60 && time<90){
  //   if (frameCount%100==0) {
  //  targets.add(new Targets(shoot, randint, pattern));
  //}
  //}
  //else if(time>90){
  //      if (frameCount%100==0) {
  //  targets.add(new Targets(shoot, randint, pattern));
  //}
  //}
  //else if(time<30){
  //          if (frameCount%120==0) {
  //  targets.add(new Targets(shoot, randint, pattern));
  //          } 
  //}
}

//All the targets functions
void target_functions() {
  for (int i = 0; i<targets.size(); i++) {
    targets.get(i).display();
    targets.get(i).destroy(shoot, aiming_Circle);
    targets.get(i).update();
    if (targets.get(i).destroyed == true) {
      //println("Object being destroyed -> target size -> ",targets.size());
      //blasterSound.play();
      boom.display(targets.get(i).x, targets.get(i).y);
      targets.remove(i);
      score.add(i);
      //println(" destroyed -> target size -> ",targets.size());
    }
  }
}

void printScore() {
  float lettersize = height*.054;
  float Yposition = height*.08125;
  float ScorePos = width*.75;
  float PointPos = ScorePos + height*.15;
  float numPos = PointPos + height*.03;
  pushStyle();
  stroke(188, 23, 23);
  strokeWeight(width*.003);
  fill(188, 166, 23, 100);
  ellipse(numPos+width*.09, Yposition-lettersize/3.1+height*.01, height*.128, height*.128);
  fill(255);
  textSize(lettersize);
  //text("Score", ScorePos, Yposition);
  //text(":", PointPos, Yposition);
  text(score.size(), PointPos+width*.095, Yposition+height*.01);
  popStyle();
}

void intro() {
}

 

Final Project Final Update

Exhibiting my work in the IM Showcase, as well as interacting with everybody’s projects was such an enjoyable experience for me. I did face some issues with the quality of the projector and the lighting after I noticed that the webcam wasn’t picking up a lot of movements because of the dim lighting. But I managed to solve at least on of those issues through setting up my phone’s flashlight next to the webcam, which increased the response of the graphics to the user’s movements. It was very rewarding to see people enjoy interacting with the project, and provide feedback about the experience. I also loved hearing Nick say that he feels like an “airbender” when interacting with the graphics, and my friends running away from the screen screaming “IT’S CHASING ME”.

At first, I had intended to lower the brightness on the laptop display, so that the user can try to figure out how to interact with the project. But unfortunately the colors displayed by the projector were a bit faded, so the graphics weren’t showing in the opacity that I originally intended to display. You can also notice the flashlight placed on the pedestal as well, which drastically improved the speed of the interaction, since it allowed the Webcam to detect figures more easily. I enjoyed observing how people tried out a range of movements with the bodies and limbs, in order to try to see the extent of the effect their movements have on the motion graphics. I’ve had people run, jump up and down and flail their arms around to see if the projection will follow their movements, so that was fun to watch.

Graphics Code:

import java.util.Calendar;
import ch.bildspur.postfx.builder.*;
import ch.bildspur.postfx.pass.*;
import ch.bildspur.postfx.*;
import oscP5.*;
import netP5.*;

PostFX fx;
//PVector center;
Particles particles;

float centerX, centerY;
float oscX, oscY;
 
OscP5 oscP5;
NetAddress myRemoteLocation;

void setup(){
  //size(displayWidth, displayHeight, P3D);
  fullScreen(P3D);
  smooth(8);
  background(0);
  
  // init form
  centerX = width/2; 
  centerY = height/2;
  
  //center = new PVector(width/2, height/2);
  fx = new PostFX(this);
  particles = new Particles(centerX, centerY);
  background(0);
  
  oscP5 = new OscP5(this,12000);
  myRemoteLocation = new NetAddress("127.0.0.1",1234);
}
 

void draw(){
  // floating towards mouse position
  
  particles.run(oscX*50, oscY*50);

  float ratio = 0.5;
  float intensity = 10;
 
//diffuse
  fx.render()
    .blur(
    round(map(ratio, 0, 1, 0, intensity)), 
    map(ratio, 1, 0, 0, intensity))
    .compose();

  //decay
  noStroke();
  fill(0, 15);
  rect(0, 0, width, height);
}
// timestamp
String timestamp() {
  Calendar now = Calendar.getInstance();
  return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}

/* incoming osc message are forwarded to the oscEvent method. */
void oscEvent(OscMessage theOscMessage) {
  /* print the address pattern and the typetag of the received OscMessage */
  print("### received an osc message.");
  print(" addrpattern: "+theOscMessage.addrPattern());
  print(" message 1: "+theOscMessage.get(0).floatValue());
  print(" message 2: "+theOscMessage.get(1).floatValue());
  println(" typetag: "+theOscMessage.typetag());
  oscX = theOscMessage.get(0).floatValue();
  oscY = theOscMessage.get(1).floatValue();

}

Particle class:

class Particle {

  PVector pos, startPos;
  float heading = random(TWO_PI);
  float startHeading = heading;
  float step = random(1.0, 5.5);
  float stepNext = step;
  float lifespan;

  color col;

  Particle() {
    this.pos = new PVector(random(width), random(height));
    this.build();
    lifespan = 255.0;
  }

  Particle(PVector pos, float heading) {
    this.pos = pos.copy();
    this.startPos = pos.copy();
    this.heading = heading;
    this.startHeading = heading;

    if (pos.x > width/2) { //change color per segment
      if (pos.y > height/2) {
        col = color(178,153,255);
      } else {
        col = color(156, 0, 128);
      }
    } else {
      if (pos.y > height/2) {
        col = color(183, 0, 183);
      } else {
        col = color(255, 132, 178);
      }
    }
  }

  Particle(PVector pos, float heading, color col) {
    this.pos = pos.copy();
    this.startPos = pos.copy();
    this.heading = heading;
    this.startHeading = heading;
    this.col = col;
  }

  void build() {
  }

  void update(float newCenterX, float newCenterY) {
    
    pos.add(newCenterX, newCenterY);
    pos.add(new PVector(cos(heading), sin(heading)).mult(stepNext));

    if (this.outOfBound()) {
      pos = startPos.copy();
      heading = startHeading;
    }
    lifespan -= 1.0;
  }

  void render() {
    //deposit
    stroke(col);
    vertex(pos.x, pos.y);
  }

  boolean outOfBound() {
    return (
    pos.x < 0 ||
    pos.x > width ||
    pos.y < 0 ||
    pos.y > height
    );
  }
  boolean isDead(){
    if (lifespan < 0.0){
      return true;
    } else {
      return false;
    } 
  }
}

Particles (mother) class:

class Particles {

  ArrayList<Particle> particles;

  Particles(float _xCenter, float _yCenter) {
    this.build(_xCenter, _yCenter);
  }

  void build(float centerX, float centerY) {
    particles = new ArrayList<Particle>();

    int steps = 30000;
    float step = TWO_PI/steps;
    float rad = 300;
    float amp = PI/8;

    for (float i = 0; i <+ steps; i++) { //add new particle at random position
      float a = step * i;
      particles.add(new Particle(
        new PVector(cos(a), sin(a)).mult(rad).add(centerX, centerY).add(PVector.random2D().mult(20)), 
        a + PI/2 + PI/8 + random(amp, amp))); //random pos amp
    }
  }

  void update(float changeX, float changeY) {
    for (Particle p : particles) {
      p.update(changeX, changeY);
      //int c = get(2, 3);
      //int r = c >> 16 & 0xFF;
      //int g = c >> 8 & 0xFF;
      //int b = c & 0xFF;
    }
  }

  void render() { //render each particle
    beginShape(POINTS);
    stroke(255);
    strokeWeight(1.0);
    for (Particle p : particles) {
      p.render();
    }
    endShape();
  }

  void run(float newX, float newY) {
    this.update(newX, newY);
    this.render();
  }
}

Webcam code Aaron helped me with:

import gab.opencv.*;
import processing.video.*;
import oscP5.*;
import netP5.*;

OscP5 oscP5;
NetAddress dest;

OpenCV opencv;
Capture video;

PVector smoothedFlow;

void setup() {
  size(320, 240);
  video = new Capture(this, 320, 240);
  opencv = new OpenCV(this, 320, 240);
  video.start(); 
  smoothedFlow = new PVector(0, 0);
  /* start oscP5, listening for incoming messages at port 12000 */
  oscP5 = new OscP5(this, 1234);
  dest = new NetAddress("127.0.0.1", 12000);
}

void draw() {
  background(0);
  if (video.available() == true) {
    video.read();
  }
  opencv.loadImage(video);
  opencv.flip(opencv.HORIZONTAL);
  opencv.calculateOpticalFlow();

  pushMatrix();
  scale(-1.0, 1.0);
  image(video, -video.width, 0);
  popMatrix();

  stroke(255, 0, 0);

  PVector aveFlow = opencv.getAverageFlow();
  PVector diff = PVector.sub(aveFlow, smoothedFlow);
  smoothedFlow.add( diff.mult(.1));

  stroke(255);
  strokeWeight(2);
  translate(width/2, height/2);
  line(0, 0, 50*smoothedFlow.x, 50*smoothedFlow.y);
  sendOsc();
}

void sendOsc() {
  OscMessage msg = new OscMessage("/opticalFlow");
  msg.add((float)smoothedFlow.x); 
  msg.add((float)smoothedFlow.y);
  oscP5.send(msg, dest);
}

It was a great semester!

Final Project User Test

I’ve probably said this on numerous occasions, but my main interest in interactive media lies in finding the intersection between art and technology. I wanted to find a way to combine everything we’ve learned in class about coding and using processing as a creative platform, with my passion for creating art and aesthetically appealing interactive installations. For this project, I wanted the user to be able to use their bodies as a medium or as a brush of some sorts, to paint on a canvas, through interacting with installation. I used a particle system, where I created an array of A LOT of objects or particles which are added in random positions along the radius of a circle revolving in the center of the screen. The interactive aspect of it, is that the webcam detects any movement on an x, y, z axis and creates movement in the graphics that mimic the user’s moves. This happens through adding a new center or circle in every place the user moves, and this circle has an intensity of blur that makes it appear to be moving smoothly with the user.

My friends having too much fun:

Despite the users enjoying the overall interaction, I noticed several aspects I could improve and build upon:

  1. The purpose of the project wasn’t clear at first, and some of the users were confused. This could be improved through using a large projection of the screen so that passersby can easily observe the effect of their movements on the graphics.
  2. Some users said that I should work on increasing the blur or changing the movement of the main circle, since they could see the trail of circles being added as they move.
  3. Adding more movement specific effects, in the sense that a smaller movement (moving an arm) would produce a different effect than moving the entire body.