Antonius: Final Project: Ghost Hunter

Final Project: Ghost Hunter

Date: May 18th

Instructor: Antonius

Aim:  Make a project that incorporate processing and Arduino in it.

Material: Processing, Arduino, Arduino kit, conductive tape, sponge, sponge mat, toy sword, accelerometer, wires.

Process:

  1. Designing: As I mentioned in my final project essay, I planned to make an interactive game. The user can control the moving of the character in the game by stepping on different part on the mat. The user can also kill enemies in the game by swing the actual sword. Initially I also designed a shield that the user can activate to block the attack from the enemies, but due to time limit and it might be too complicate for the plays to control so many controllers, I abandoned this part.
  2. Making the game:

I started by making the game in Processing first. Instead of using actual physical input, I use keyboard input first when building the game. The game is consisted of two components, the player and the enemies. I used the function keyPressed to control the character to move in four direction, by pressing the four keys “w”, “a”, “s”, “d”.

When making the enemies, at first I created them to let them chase the character. One problem with it is that all the enemies would overlap with each other after following the character for some time. To deal with this problem I tried several approaches, like detecting the distance between each enemy, but failed. In the end, I decided to let the enemies run randomly and let the character to catch them.

The enemies were made using object in processing. I defined all the characteristics for the enemies in a separate class, including color, position, speed, behavior after running into the character, etc. Then I use array list to call the enemies. In that case, each enemy could behave on their own, and when they are supposed to be killed, I could simply remove them from the list.

  1. Creating the input structure

As I mentioned in my design, I planned to let users to use their body movements to control the action of the character. The moving of the character was designed to be controlled by stepping on a stepping board on the ground. To control the character to move un, down, left and right, the players have to step on the front, back, left, right panels on the board. Originally I decided to use force sensor to be the information input, but considering that stepping might cause breaking the force sensor, I changed my mind to use soft button as input. With the help from professor Antonius, I was able to make my own soft button. Two conductive materials slicked on two separated boards, a sponge with a hole in it was used to connect the two boards. In this way, when stepping on the board, the conductive materials will get connected from the hole in the sponge, forming a whole loop that conducts current, which used to send input information to Arduino.

IMG_1599

The next step is to control the attack effect of the character. I used the accelerometer as input. When the user is swing the sword, the change in acceleration will be sensed and send to processing to trigger an “attack zone” around the character. And when the enemies overlapping with the “attack zone”, they will be eliminated.

IMG_1634

  1. Improving gaming experience

To make my game more playable, I add a time limit to the game. If the player can kill all the enemies in the limiting time, a screen showing “you win” will pop out; and if the enemies are not all killed in the time limit, a screen showing “game over” will pop out. What’s more to make the game more responsive, I also added sound effect when the enemies bounced against the character, and a sword swing effect when the user is swing the sword.

The pictures and actual effects are shown below.

IMG_1633

IMG_1635

IMG_1639

IMG_1637

//This is the processing code
import processing.serial.*;
Serial myPort;
int valA, valB, valC, valD;

ArrayList<Car> cars;
int x, y;
int x1, y1, a1, b1;


void setup() {
  myPort = new Serial(this, Serial.list()[2], 9600);
  size(720, 540);
  x = width/2;
  y = height/2;
  rectMode(CENTER);

  cars = new ArrayList<Car>();
  for (int i = 0; i < 10; i++) {
    cars.add(new Car(color(255, 0, 0), random(0, width), random(0, height), 2, 1));
  }
}

void draw() { 
  while(myPort.available() > 0){
     valA = myPort.read();
     valB = myPort.read();
     valC = myPort.read();
     valD = myPort.read();
  }
  background(100);
  //rect(x1, y1, a1, b1);
  fill(255);
  ellipse(x, y, 50, 50);
  
  if (valA > 100) {
    x = x - 3;
    x1 = x - 20;
    y1 = y;
   // a1 = 80;
   //b1 = 50;
  }
  if (valB > 100) {
    y = y - 3;
    x1 = x;
    y1 = y - 20;
    //a1 = 50;
    //b1 = 80;
  } 
  if (valC > 100) {
    x = x + 3;
    x1 = x + 20;
    y1 = y;
    //a1 = 80;
    //b1 = 50;
  } 
  if (valD > 100) {
    y = y + 3;
    x1 = x;
    y1 = y + 20;
    //a1 = 50;
    //b1 = 80;
  }
    
  if (y < 25){
    y = y + 3;
  }
  if (y > height - 25){
    y = y - 3;
  }
  if (x < 25){
    x = x + 3;
  }
  if (x > width - 25){
    x = x - 3;
  }
    

  for (int i = 0; i < cars.size(); i++) {
    Car car = cars.get(i);
    car.drive();
    car.display();
    if (dist(car.xpos, car.ypos, x, y) < 35) {
      cars.remove(i);
    }
  }
}


class Car { 
  color c;
  float xpos;
  float ypos;
  float xspeed;
  float yspeed;


  // The Constructor is defined with arguments.
  Car(color tempC, float tempXpos, float tempYpos, float tempXspeed, float tempYspeed) { 
    c = tempC;
    xpos = tempXpos;
    ypos = tempYpos;
    xspeed = tempXspeed;
    yspeed = tempYspeed;
  }

  void display() {
    stroke(0);
    fill(c);
    ellipse(xpos, ypos, 20, 20);
  }

  void drive() {
    xpos = xpos + xspeed;
    if (xpos > width) {
      xspeed = -xspeed;
    } else if (xpos < 0) {
      xspeed = -xspeed;
    }
    ypos = ypos + yspeed;
    if (ypos > height) {
      yspeed = -yspeed;
    } else if (ypos < 0) {
      yspeed = -yspeed;
    }
  }
}


//This is the Arduino code
int valA, valB, valC, valD, valE;

#include <Wire.h>
#include <ADXL345.h>


ADXL345 adxl;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  adxl.powerOn();

  //set activity/ inactivity thresholds (0-255)
  adxl.setActivityThreshold(75); //62.5mg per increment
  adxl.setInactivityThreshold(75); //62.5mg per increment
  adxl.setTimeInactivity(10); // how many seconds of no activity is inactive?
 
  //look of activity movement on this axes - 1 == on; 0 == off 
  adxl.setActivityX(1);
  adxl.setActivityY(1);
  adxl.setActivityZ(1);
 
  //look of inactivity movement on this axes - 1 == on; 0 == off
  adxl.setInactivityX(1);
  adxl.setInactivityY(1);
  adxl.setInactivityZ(1);
 
  //look of tap movement on this axes - 1 == on; 0 == off
  adxl.setTapDetectionOnX(0);
  adxl.setTapDetectionOnY(0);
  adxl.setTapDetectionOnZ(1);
 
  //set values for what is a tap, and what is a double tap (0-255)
  adxl.setTapThreshold(50); //62.5mg per increment
  adxl.setTapDuration(15); //625us per increment
  adxl.setDoubleTapLatency(80); //1.25ms per increment
  adxl.setDoubleTapWindow(200); //1.25ms per increment
 
  //set values for what is considered freefall (0-255)
  adxl.setFreeFallThreshold(7); //(5 - 9) recommended - 62.5mg per increment
  adxl.setFreeFallDuration(45); //(20 - 70) recommended - 5ms per increment
 
  //setting all interrupts to take place on int pin 1
  //I had issues with int pin 2, was unable to reset it
  adxl.setInterruptMapping( ADXL345_INT_SINGLE_TAP_BIT,   ADXL345_INT1_PIN );
  adxl.setInterruptMapping( ADXL345_INT_DOUBLE_TAP_BIT,   ADXL345_INT1_PIN );
  adxl.setInterruptMapping( ADXL345_INT_FREE_FALL_BIT,    ADXL345_INT1_PIN );
  adxl.setInterruptMapping( ADXL345_INT_ACTIVITY_BIT,     ADXL345_INT1_PIN );
  adxl.setInterruptMapping( ADXL345_INT_INACTIVITY_BIT,   ADXL345_INT1_PIN );
 
  //register interrupt actions - 1 == on; 0 == off  
  adxl.setInterrupt( ADXL345_INT_SINGLE_TAP_BIT, 1);
  adxl.setInterrupt( ADXL345_INT_DOUBLE_TAP_BIT, 1);
  adxl.setInterrupt( ADXL345_INT_FREE_FALL_BIT,  1);
  adxl.setInterrupt( ADXL345_INT_ACTIVITY_BIT,   1);
  adxl.setInterrupt( ADXL345_INT_INACTIVITY_BIT, 1);
}

void loop() {
  // put your main code here, to run repeatedly:
  //Serial.println(analogRead(0));
int x,y,z;  
  adxl.readXYZ(&x, &y, &z); //read the accelerometer values and store them in variables  x,y,z
  double xyz[3];
  double ax,ay,az;
  adxl.getAcceleration(xyz);
  ax = xyz[0];
  ay = xyz[1];
  az = xyz[2];
  
  
  if (ax < -1.2 || ax > 1.2 || ay < -1.2 || ay > 1.2 || az < -1.2 || az > 1.2){
    valE = 1;
  }
  else{
    valE = 0;
  }
  
  
  valA = map(analogRead(0), 0, 1023, 0, 255);
  valB = map(analogRead(1), 0, 1023, 0, 255);
  valC = map(analogRead(2), 0, 1023, 0, 255);
  valD = map(analogRead(3), 0, 1023, 0, 255);
  Serial.write(valA);
  //delay(10);
  Serial.write(valB);
  //delay(10);
  Serial.write(valC);
  //delay(10);
  Serial.write(valD);
  Serial.write(valE);
  delay(30);
  
  
}

Color Landscape with Perlin Noise

I used an ASCII code for serial communication from Arduino to Processing, which changed the colors and speed of a Perlin noise animation.

Circuit:

IMG_3540

  • red cable connects power to 5V
  • green cables connect tilt sensors to power (green switches are side-to-side, white switches are up-and-down)
  • black cable connects ground to ground
  • 10 k resistors connect switches to ground
  • blue cables connect breadboard pins to Arduino pins:
    • breadboard pin 8 to digital pin 8 = Left (1,0)
    • breadboard pin 18 to digital pin 7 = Right (0,1)
    • breadboard pin 23 to digital pin 13 = Up (0, 1)
    • breadboard pin 27 to digital pin 12 = Down (1,0)

*testing the side-to-side values by commenting out the up and down values

3D Perlin noise:

Code Source: Processing 3D Noise example

*below the increment is changed to -.01 and a void mousePressed(){ } function added to change from greyscale to color

Link: https://processing.org/examples/noise3d.html

int x = 0;
int y = 0;
float increment = -.01;
//noise function argument #3 (a global variable that increments one per cycle)
float zoff = 0.0; //incremement zoff != xoff or yoff
float zincrement = 0.02;

void setup() {
size(640, 640);
background(0);
frameRate(30);
}

void draw() {
//adjust noise detail
noiseDetail(8, 0.65f);
loadPixels();
float xoff = 0.0; //start xoff at 0
//for every x, y coordinate in a 2D space, calculate a noise value
//and display a brightness value
for (int x = 0; x < width; x++) {
xoff += increment; //increment xoff
float yoff = 0.0; //for every xoff, start yoff at 0
for (int y = 0; y < height; y++) {
yoff += increment; // increment yoff
// calculate noise and scale by 255
float bright = noise(xoff, yoff, zoff)*255;
// set each pixel onscreen to a grayscale value
pixels[x+y*width] = color(bright, bright, bright);
}
}
updatePixels();
zoff += zincrement; // increment zoff
}
void mousePressed() {
fill(pixels[x+y*width] = color(255));
}

A few times people have described tinnitus to me as the sound that noise looks like on a TV. I found a code for 3D Perlin noise to generate an index of pixels on the screen that look like noise but can be manipulated to simulate textural gradients. Below is a diagram of 2D noise, showing the pixel coordinates in the algorithm, moving in the direction of the arrows. The arrows represent the xoff and yoff variables in the code, controlling the direction in which the pixels appear to randomly move in during the for() loop. The math explains the calculation for this randomness: the gradient is made up of vectors, so the code uses vector coordinates from within each cube (3D pixel) and a point on its edges.[1] The code generates a pseudorandom vector gradient, so the noise pixels seem to move randomly but actually move the same way according to the input integers.[2]

Screen Shot 2017-05-20 at 10.48.20 AM

image source: https://gamedev.stackexchange.com/questions/23625/how-do-you-generate-tileable-perlin-noise

[1] http://flafla2.github.io/2014/08/09/perlinnoise.html

[2] http://flafla2.github.io/2014/08/09/perlinnoise.html

The noiseDetail function changes the appearance by increasing the number of pixels and decreasing their size, so that the grid of pixels is denser. Because the increment functions, controlling the rate at which the pixels move, decreased the frameRate from 30 to a range between 5 and 10, I tried to change it as little as possible. Instead, I focused on color and sound because I couldn’t marble the texture without decreasing the frameRate. I planned to make a pixel that marbles the noise texture in the direction of the maze’s motion, signaled by the tilt switches on the on the bottom of the maze, but the cables kept coming out. Then I decided to keep the cloudy texture and make a changing noise landscape.

*above: this is the only video with sound; when I wanted to represent the player with the marble as a pixel, I used (pmouseX, pmouseY, mouseX, mouseY) to draw a line tracing the movement to change the noise from the library example PinkNoise along with the pixel index statement, color(1*mouseX/1.5,bright,(mouseY/1.5)*255), to change with the sound amplification.

*below: I drew a rectangle with the mouse because I didn’t like the idea of representing the player, so I considered drawing its perspective in the maze by connecting lines from the corners of each angle to the corners of the screen. I decided to just work on the animation as a separate project; my ideas for the maze and the noise seemed better off separate.

I tested the code with boolean statements, then replaced them with if ( ){} / }else if( ){ function for the serial communication. With the if/else function for the side-to-side tilt, the left switch changed the screen from colorful noise, indicated by println(“flat”) in Processing and (0 , 0) from the Arduino, to saturated hues; separately, the if/else statement for the right tilt switched the screen from the same colorful noise – showing that the board was not tilted – to a more striated, and consequently more slowly moving, yellow screen. I multiplied the yoff increment by 5 to stretch the bands of color sideways, across the x-axis.

Screen Shot 2017-05-20 at 2.10.20 AM

*noise with the xoff increment increased

I worked on making layers with different variables and using the map function, but both decreased the frameRate. In the version that I presented, the color mode changes from HSB for the left tilt and to RGB for the right so that the color shifts from saturated while tilted left, to grainy while flat, and to RGB color while tilted right. I added a Perlin noise wave also to cover the screen where the up tilt switch meets the side to side, so that the noise wave would appear like a wavey landscape whenever the tilt switch pointed down. Processing crashed before I saved my most recent edit, which changed several of the vertices so that the wave was wider at the edges of the screen.

Source for 2D Noise Wave: https://processing.org/examples/noisewave.html

*original code in Processing examples

float yoff = 0.0;       // 2nd dimension of perlin noise

void setup() {

size(640, 360); }

void draw() {

background(51);

fill(255);   // We are going to draw a polygon out of the wave points

beginShape();

float xoff = 0;       // Option #1: 2D Noise

// float xoff = yoff; // Option #2: 1D Noise

// Iterate over horizontal pixels   for (float x = 0; x <= width; x += 10) {

// Calculate a y value according to noise, map to

float y = map(noise(xoff, yoff), 0, 1, 200,300); // Option #1: 2D Noise

// float y = map(noise(xoff), 0, 1, 200,300);   // Option #2: 1D Noise

// Set the vertex

vertex(x, y);

// Increment x dimension for noise

xoff += 0.05;   }

// increment y dimension for noise

yoff += 0.01;

vertex(width, height);

vertex(0, height);

endShape(CLOSE); }

To improve this project, I would use different sensors to shift the colors more smoothly. I think that analog would work better for this project than digital, so that the colors would change with a range of values up to 255 instead.

ARDUINO tilt sensor:

int buttonPinL = 7;    //button pin left
int buttonPinR = 8;   //button pin right

int buttonPinU = 13;  //button pin up
int buttonPinD = 12;  //button pin down


void setup() {
  // put your setup code here, to run once:
  pinMode(buttonPinL, INPUT);
  pinMode(buttonPinR, INPUT);
  pinMode(buttonPinU, INPUT);
  pinMode(buttonPinD, INPUT);

  Serial.begin(9600); //baud rate

}

void loop() {

  //LEFT RIGHT 
int valueL = digitalRead(buttonPinL);
Serial.print(valueL);                     //read serial value L (1/0)
Serial.print(",");              //separate the values

//update value
int valueR = digitalRead(buttonPinR);    //read serial value R (0/1)
Serial.print(valueR);
Serial.print(",");              //separate the values

  //UP DOWN
int valueU = digitalRead(buttonPinU);
Serial.print(valueU);                       //read serial value U (0/1)
Serial.print(",");                  //separate the values

//update value
int valueD = digitalRead(buttonPinD);       //read serial value D (1/0)
Serial.print(valueD);

Serial.println();
delay(10); //delay 10 milliseconds

}

PROCESSING:

import processing.serial.*;
String myString;    //0/1 values in Arduino
Serial myPort;


//SERIAL COMMUNICATION FROM ARDUINO
int tiltL;     //declare variables for left and right
int tiltR;
int tiltU;    //declare variables for up and down
int tiltD;

//PERLIN NOISE
int x = 0;
int y = 0;
int Wx = 0; //
int Wy = 0;

float xoff;     //for manipulation later
float yoff;
float increment = .01;
float xincrement = .02;    //variable for x increment
//noise function argument #3 (a global variable that increments one per cycle)
float zoff = 0.0; //incremement zoff != xoff or yoff
float zincrement = 0.02;
float bright;
float Wyoff = 0.0; //

void setup() {
  background(0);
  size(640, 640);
  frameRate(30);     //30sec new random location for pixels

  printArray(Serial.list()); //list serial devices
  myPort = new Serial(this, Serial.list()[1], 9600); //new port, common data rate
  //clear out the buffer of the port
  myPort.clear();
}

void draw() {
  while (myPort.available() > 0) { //available function (not a variable)
    //put what is in my port into the string
    myString = myPort.readStringUntil(10); //10 ASCII = Serial.println
    //    println(myString);
    //condition to test whether my string is null
    if (myString != null) {
      //      println(myString);
      //split and trim data from Arduino
      String[] data = split(trim(myString), ","); //data from Arduino, split by commas
      //use a loop to print data
      //      print(data);
      for (int i = 0; i < data.length; i++) { //initial value, length of data array
        print(data[i]);
        print(",");
      }
      tiltL = int(data[0]); //turn data into an integer
      tiltR = int(data[1]);  
     tiltU = int(data[3]);
     tiltD = int(data[4]);
      println();
    }
  }
//LEFT TILT saturated color
  if (tiltL == 1) { 
   colorMode(HSB);
    noiseDetail(8, 0.65f);
    loadPixels();
    float xoff = 0.0; //start xoff at 0
    for (int x = 0; x < width; x++) {
      xoff += increment;               //increment xoff
      float yoff = 0.0;                 //for every xoff, start yoff at 0
      for (int y = 0; y < height; y++) {
        yoff += increment;               // increment yoff
        float bright = noise(xoff, yoff, zoff)*255;
        // set each pixel onscreen to a grayscale value
        pixels[x+y*width] = color(bright, bright, 255);
      }
    }
    updatePixels();
    zoff += zincrement; // increment zoff
     println("left");
 //FADE SATURATION INTO STATIC
  } else if (tiltL == 0) { 
       colorMode(RGB);
    noiseDetail(8, 0.65f);
    loadPixels();
    float xoff = 0.0; //start xoff at 0
    for (int x = 0; x < width; x++) {
      xoff += increment;               //increment xoff
      float yoff = 0.0;                 //for every xoff, start yoff at 0
      for (int y = 0; y < height; y++) {
        yoff += increment;               // increment yoff
        float bright = noise(xoff, yoff, zoff)*255;
        float hue = noise(xoff, yoff, bright)*255;
        // set each pixel onscreen to a grayscale value
        pixels[x+y*width] = color(bright, hue, 255);
      }
    }
    updatePixels();
    zoff += zincrement; // increment zoff
      println("flat");
  }
//FADE STATIC INTO COLOR
    if (tiltR == 0) { 
    noiseDetail(8, 0.65f);
    loadPixels();
    float xoff = 0.0; //start xoff at 0
    for (int x = 0; x < width; x++) {
      xoff += increment;               //increment xoff
      float yoff = 0.0;                 //for every xoff, start yoff at 0
      for (int y = 0; y < height; y++) {
        yoff += increment;               // increment yoff
        float bright = noise(xoff, yoff, zoff)*255;
        float hue = noise(xoff, yoff, bright)*255;
        pixels[x+y*width] = color(bright, hue, 255);
      }
    }
    updatePixels();
    zoff += zincrement; // increment zoff
      println("flat");
 //RIGHT TILT RGB color
  } else if (tiltR == 1) {
  noiseDetail(8, 0.65f);
  loadPixels();
  float xoff = 0.0; //start xoff at 0
  for (int x = 0; x < width; x++) {
    xoff += increment;               //increment xoff
    float yoff = 0.0;                 //for every xoff, start yoff at 0
    for (int y = 0; y < height; y++) {
      yoff += increment;               // increment yoff
  float bright = noise(xoff, yoff, zoff)*255;
    pixels[x+y*width] = color(bright, 255, 139);
  }
    }
updatePixels();
zoff += zincrement; // increment zoff
  println("right");
}

//draw sky
if(tiltU == 1){
    noiseDetail(8, 0.65f);
  loadPixels();
  float xoff = 0.0; //start xoff at 0
  for (int x = 1; x < width; x++) {
    xoff += increment;               //increment xoff
    float yoff = 0.0;                 //for every xoff, start yoff at 0
    for (int y = 1; y < height/3; y++) {
      yoff += 2.5*increment;               // increment yoff
  float bright = noise(xoff, yoff, zoff)*255;
 pixels[x+y*width] = color(135, 150, bright);
  }
  }
  updatePixels();
  zoff += zincrement; // increment zoff
  println("up");
}else if(tiltU == 0){
    noiseDetail(8, 0.65f);
  loadPixels();
  float xoff = 0.0; //start xoff at 0
  for (int x = 0; x < width; x++) {
    xoff += increment;               //increment xoff
    float yoff = 0.0;                 //for every xoff, start yoff at 0
    for (int y = 0; y < height/3; y++) {
      yoff += 5*increment;               // increment yoff
  float bright = noise(xoff, yoff, zoff)*255;
 pixels[x+y*width] = color(255, 255, bright);
  }
  }
  updatePixels();
  zoff += zincrement; // increment zoff
println("level");
}
//draw noise wave as landscape
if(tiltD == 1){
  fill(255);
  beginShape();
  float Wxoff = 0;
  for (float Wx = 0; Wx <= width; Wx += 10) {
    float Wy = map(noise(Wxoff, Wyoff), 0, 1, 200,300);
    vertex(Wx, Wy);
    Wxoff += 0.05;
  }
  Wyoff += 0.01;
  vertex(width, height/2);
  vertex(0, height/2);
  endShape(CLOSE);
  println("down");
}else if(tiltD == 0){
println("level");
}
}

Final Project Documentation

Instructor: Professor Daniel Mikesell

Poopyturd

An Interactive Game, or, “Life is shit, play as one”

Description:

This game follows the adventures of Poopyturd, the turd, as he navigates through the treacherous sewer system. The character is controlled by distance from the toilet (or controller).

Conceptual Development:

I had a really hard time figuring out what to do for my final project and I happened to make a joke about that – thus, the concept was born. The concept of Poopyturd is kind of ridiculous which is exactly what life is like. I have yet to decide if that is a comment on life or a celebration of it. I just remade flappybird but made it a turd. I stuck with it because it was funny, but more so because it was just a silly thing to do.

Stylistically, you can very easily see where I put my stamp on it. The music choice (Bubble Dream by CHON), the water spiral that serves as a background and the toilet controller all make it kind of an interesting interface, but it is still super crude. Poopyturd is, in a sense, an experiment in how far self-aware humor, or lack thereof,  can take you. It is an interactive art piece in the ridiculousness of life. But it is also just a poop.

Technical Development:

I really had no idea what I wanted this to look like at first, so a lot of my stylistic elements were added way after I established the logic of the game. I took cues from examples I found online and first worked on coding different game screens. I then added an image of the poop emoji as the character and created various wall functions – to draw the obstacles, watch collisions and keep it scrolling. That was slightly difficult to dissect out of examples and so it was some trial and error to understand it.

Getting the distance sensor to work with the game correctly was probably the hardest thing I had to deal with. At first, I just played around with ranges and set it so that if the sensor read a value greater than or equal to 150, poopyturd would move up and if it was less than that he would move down. This worked, but not quite how I intended. It just acted as a motion sensor instead of requiring the player to move their hand over it vertically. I asked for some guidance, but I was just told to finesse the values a little more. Finally, I thought about trying to map the sensor values to the screen. That worked.

Creating the toilet controller was a lot of fun. I used cardboard and half of a water bottle to build the base and then covered it with foam and more cardboard pieces to add detail.

I was trying to decide whether or not to use the song Internet Bounce by 2ToneDisco, but that was too random, so I went with Bubble Dream. I made the obstacles into toilets and I added a spiral I made in another processing sketch/saved as a screen capture as the game screen background.

Originally, I wanted to 3D print a plunger and use a pressure sensor to affect speed. I decided to use a real plunger I bought instead, but I could not get it to work in time.

Lessons Learned:

Games are hard to code and require a lot more time to look good. Simple fixes are great. Trial and error is everything.

Arduino Code:

int sensorpin = 0;
int val = 0;

void setup() {
  // put your setup code here, to run once:
Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
val = analogRead(sensorpin);
Serial.println(val);
delay(25);
}

Media:

IMG_4054 IMG_4055 IMG_4056 IMG_4071 IMG_4073 IMG_4075 IMG_4076

IMG_4079

import processing.serial.*;
import processing.video.*;


import ddf.minim.*;
import ddf.minim.analysis.*;
import ddf.minim.effects.*;
import ddf.minim.signals.*;
import ddf.minim.spi.*;
import ddf.minim.ugens.*;

Minim minim;
AudioPlayer song;

float poopX, poopY;
float time = 0;
int gameScreen = 0;
PImage poop;
PImage swirl;

int wallSpeed = 3;
int wallInterval = 1700;
float lastAddTime = 0;
int minGapHeight = 80;
int maxGapHeight = 200;
int wallWidth = 80;
ArrayList<int[]> walls = new ArrayList<int[]>();

int maxHealth = 100;
float health = 100;
float healthDecrease = 1;
int healthBarWidth = 60;
int speedY = 3;

poop p;
swirl s;

float[] sensorValues = {0};
Serial myPort;

/********* SETUP BLOCK *********/

void setup() {
  size(680, 500);
  frameRate(60);
  poopX = width/4;
  poopY = height/2;
  
  poop = loadImage("poop.png");
  swirl = loadImage("background.png");
  
  printArray(Serial.list());
  
  myPort = new Serial(this, Serial.list()[1], 9600);
  
  myPort.bufferUntil('n');
  
  minim = new Minim(this);
  song = minim.loadFile("bubble dream.mp3");
}


/********* DRAW BLOCK *********/

void draw() {
  // Display the contents of the current screen
  if (gameScreen == 0) {
    initScreen();
  } else if (gameScreen == 1) {
    gameScreen();
  } else if (gameScreen == 2) {
    gameOverScreen();
  }
}


/********* SCREEN CONTENTS *********/

void initScreen() {
  background(0);
  pushMatrix();
  translate(width/2, height/2);
  stroke(255);
  textSize(48);
  textAlign(CENTER, CENTER);
  text("Press Enter", 0, 0);
  popMatrix();
  
}
void gameScreen() {
  background(255);
  
  s = new swirl();
  s.drawSwirl();
  
  pushMatrix();
  p = new poop();
  p.drawPoop();
  popMatrix();
  
  song.play();
  
  charactercontrol();
  
  wallAdder();
  wallHandler();
  
  drawHealthBar();
  
  if (health <= 0){
    gameOverScreen();
  }
 
  
}
void gameOverScreen() {
  background(0);
  fill(255);
  translate(width/2, height/2);
  textSize(48);
  textAlign(CENTER, CENTER);
  text("GAME OVER", 0, 0);
  
  song.close();
}


/********* INPUTS *********/

void keyPressed(){
  if(key == ENTER) {
    if(gameScreen==0) {
    startGame();
  }
}
}

  void charactercontrol() {
    poopY = map(sensorValues[0], 680, 0, 0, height);
  //if (sensorValues[0] >= 150) {
  //    poopY = poopY - speedY;
  //  } else if (sensorValues[0] <= 100) {
  //    poopY = poopY + speedY;
  //}
}

/********* OTHER FUNCTIONS *********/

// This method sets the necessery variables to start the game  
void startGame() {
  gameScreen=1;
}


void wallAdder() {
  if (millis()-lastAddTime > wallInterval) {
    int randHeight = round(random(minGapHeight, maxGapHeight));
    int randY = round(random(0, height-randHeight));
    // {gapWallX, gapWallY, gapWallWidth, gapWallHeight}
    int[] randWall = {width, randY, wallWidth, randHeight}; 
    walls.add(randWall);
    lastAddTime = millis();
  }
}
void wallHandler() {
  for (int i = 0; i < walls.size(); i++) {
    wallRemover(i);
    wallMover(i);
    wallDrawer(i);
    
    watchWallCollision(i);
  }
}
void wallDrawer(int index) {
  int[] wall = walls.get(index);
  // get gap wall settings 
  int gapWallX = wall[0];
  int gapWallY = wall[1];
  int gapWallWidth = wall[2];
  int gapWallHeight = wall[3];
  // draw actual walls
  rectMode(CORNER);
  //stroke(0);
  strokeWeight(3);
  fill(#9BA6AA);
  rect(gapWallX, 0, gapWallWidth, gapWallY);
  arc(gapWallX - 10, gapWallY + gapWallHeight, gapWallWidth + 4, 100, 0, PI, CHORD);
  rect(gapWallX, gapWallY+gapWallHeight, gapWallWidth, height-(gapWallY+gapWallHeight));
}
void wallMover(int index) {
  int[] wall = walls.get(index);
  wall[0] -= wallSpeed;
}
void wallRemover(int index) {
  int[] wall = walls.get(index);
  if (wall[0]+wall[2] <= 0) {
    walls.remove(index);
  }
}
void decreaseHealth(){
  health -= healthDecrease;
  }

void watchWallCollision(int index) {
  int[] wall = walls.get(index);
  // get gap wall settings 
  int gapWallX = wall[0];
  int gapWallY = wall[1];
  int gapWallWidth = wall[2];
  int gapWallHeight = wall[3];
  int wallTopX = gapWallX;
  int wallTopY = 0;
  int wallTopWidth = gapWallWidth;
  int wallTopHeight = gapWallY;
  int wallBottomX = gapWallX;
  int wallBottomY = gapWallY+gapWallHeight;
  int wallBottomWidth = gapWallWidth;
  int wallBottomHeight = height-(gapWallY+gapWallHeight);

  if (
    (poopX >= wallTopX) &&
    (poopX <= wallTopX+wallTopWidth) &&
    (poopY >= wallTopY) &&
    (poopY <= wallTopY+wallTopHeight)
    ) {
    decreaseHealth();
  }
  
  if (
    (poopX >= wallBottomX) &&
    (poopX <= wallBottomX+wallBottomWidth) &&
    (poopY >= wallBottomY) &&
    (poopY <= wallBottomY+wallBottomHeight)
    ) {
    decreaseHealth();
  }
}

void drawHealthBar() {
  noStroke();
  fill(236, 240, 241);
  rectMode(CORNER);
  rect(poopX -(healthBarWidth/2), poopY - 30, healthBarWidth, 5);
  if (health > 60) {
    fill(46, 204, 113);
  } else if (health > 30) {
    fill(230, 126, 34);
  } else {
    fill(231, 76, 60);
  }
  rectMode(CORNER);
  rect(poopX -(healthBarWidth/2), poopY - 30, healthBarWidth *(health/maxHealth), 5);
}

void serialEvent(Serial myPort) { 
  String inString = myPort.readStringUntil('n'); 
  if (inString != null) {
    inString = trim(inString); 
    sensorValues[0] = int(inString);
  }
    println(sensorValues);
}

--------------------------------------------
class poop {
  void drawPoop() {
  imageMode(CORNER);
  image(poop, poopX, poopY);
}
}
--------------------------------------------
class swirl {
  void drawSwirl() {
  imageMode(CENTER);
  image(swirl, width/2, height/2);
}
}

Final Project – Your Solar System

Tittle: Your Solar System

Instructor: Moon

Goals and Aims:

The goal of this lab is to create a solar system using Processing, Leap Motion and Arduino. Processing is used for the display of the planets in space, Leap Motion is used to “grab” the planets and place them wherever the user wants to, and Arduino is used to move around the space using an Accelerometer which is attached to a hat that the user has to wear. Moreover, I laser cut a rectangle of acrylic which had the same measurements as my laptop’s screen in order to create a Pepper’s Ghost effect.

The purpose of using Leap Motion and the Pepper’s Ghost effect is making the user’s experience more futuristic. I did not want to have any physical object which the user had to hold while interacting with my project, which is why I decided to place the Accelerometer on a hat instead of creating a controller.

Materials: 

Processing, Arduino, 1 DFRduino, 1 3-axis accelerometer, 4 jumper cables of 2m long, acrylic, 1 hat, cardboard, 1 cardboard box, black paper, double-sided tape, 1 needle, thread, glue gun.

DEMO:

DFRduino:

Since I used an accelerometer, all I had to do is connect it to DFRduino. The 3-axis accelerometer already has 4 jumper cables attached to it. One for the 5v power (red), one for the ground (black), and the other two for the digital values (white and yellow). I used a red jumper cable to connect the accelerometer to the DFRduino’s 5V input pin, a black cable to connect the its ground to one of the DRFduino’s GND input pin, a blue cable (which corresponded to the white cable of the accelerometer) to connect it to the digital input pin SDA and a green cable (which corresponded to the yellow cable of the accelerometer) to connected it to connect it to the digital input pin SCL. (These pins are found in the back of the DFRduino)

IMG_1715

I needed to attach the accelerometer to a hat, so I asked Marcela and she helped me sew it to the hat. And lastly, I used the glue gun to glue the jumper cable from DFRduino to the ones from the accelerometer so that they would not detach from each other.

IMG_1766IMG_1712IMG_1767

Arduino:

In order to be able to use the accelerometer, I had to add a library called ADXL345.h to my code. I used the ADXL345_demo_code as a sample code which was included in the library, and only changed a couple of things for my project, so my Arduino code was not very complicated. For instance, in void loop() I deleted the Serial.print of the acceleration values and only printed  the accelerometer values instead. And instead of delaying the code by 500 ms, I delayed it by 100ms because when I finally connected Processing and Arduino together, I realized the frameRate of my Processing code was being slowed doing because of the delay in my Arduino code.

Processing:

Serial Communication and navigating in the space: I first added the Serial Communication library. I created a String called myString and a Serial called myPort, an int for the number of sensors I had, another int for values[], and six float values: accX, accY, accZ, adjX, adjY, and adjZ ( this last one was set equal to 255). In the setup, I printed an array of the ports in order to know which one was my DFRduino. In the draw, I used a while loop to read the Arduino values until 10, which is the value for an enter/new line of print for each of the sensor. Inside the while loop, I also declared accX, accY, and accZ to be equal to the sensor values plus adjX, adjY, and adjZ, respectively. I then created if statements so that if the value of the accX, or accZ were greater than 50 or less that -50, depending on the angle at which the accelerometer was tilted to, the planets would translate accordingly. I then constrained the translating values of the x-axis and the z-axis. On the picture below you can see the accelerometer and the processing sketch. It is very tiny but the white text in the middle of the background is the values of the accelerometers.

IMG_1709

Leap Motion: On a new tab, I added the Leap Motion for Processing library. I created 4 float variables (handX, handY, handZ, and grab) and a PVector for the position of the joints of a hand. I created a function called leapRun where I made a for loop to set handX, hand Y, and handZ equal to the given position of the hand from the library, and I set grab equal to the grab strength value from the library. Inside this for loop, I made two other for loops where I equal the PVector values to their corresponding value from the library, and used these values to draw strokes over them so that the user would be able to see his hand when opening the sketch. Then I created an if statement to create a difference between when the grabbing strength is more less than 0.8 and when it is more than 0.8. Outside of this function, I made another function called drawHand were I drew a sphere that is later used to grab the planets, and translated the sphere to the handX, handY and handZ positions. The lines in the photograph bellow are the joints of the hand. In the setup of the code, I set leap equal to new LeapMotion(this), and in the draw function I called the functions leapRun and drawHand.

Screen Shot 2017-05-24 at 12.06.29 AMScreen Shot 2017-05-24 at 12.08.59 AM

No grabbing                           vs                            Grabbing

Planets: On a separate tab I created a class called Planet. I created the following float values: x , y, z, size, orbitDistance, angle and speed, and created two booleans, one named nearSun and the other isOrbiting. Then I defined the class with x, y, z, size and speed variables and set nearSun to false. Then I created a function called displayHalf, where I drew the planet shown in picture 1. This planet is the combination of a sphere and an ellipse. In order to make the ellipse rotate in the same position as the sphere, I created the ellipse inside the Matrix used for the sphere, and rotated it in the y-axis. Another function I created is displayStone. This is were I drew the grey planet that looks like a stone. To create the shape shown in picture 2, I used two for loops with the int values angle and angleY, and then rotated the boxes on the y-axis by angle and on the x-axis by angleY. I also set the fill color to be random between 150 and 255 so that it would look like the planet is shinning. Then I created another function called displayStar. This is the function for the yellow and black planet that looks like a star, seeing in picture 3. I had to use many push and pop Matrices because this shape is created with 7 ellipses, and each one of them is rotated on a different angle in either one or two axises. They also all rotate at the same speed and in the same direction. As it is shown in picture 4, the planet is a cone and corresponds to the function displayCone. To draw this figure I used a code I found in a processing forum. The function for the dimensions of this shape is created on a separate tab and is called cone. The next planet I created in seen in picture 5. This planet is a cube made of cubes and I created it using three for loops using x, y, and z values inside the function called displayCube, and I translated the each of the cubes by these values. In the setup, I called each of the functions for the planets and defined their variables’ values. In the draw function, I simply called each of the functions that displayed the planets.

Screen Shot 2017-05-18 at 9.56.55 AMScreen Shot 2017-03-29 at 10.39.05 PMScreen Shot 2017-05-18 at 9.57.15 AMScreen Shot 2017-05-18 at 9.57.08 AMScreen Shot 2017-05-18 at 9.57.00 AM

Picture 1.                    Picture 2.                 Picture 3.                  Picture 4.            Picture 5.

Sun: The figure shown in picture 6 is a sphere made of ellipses and is supposed to be like the sun, because all the planets orbit around it. I created the class EllisO and gave the function x, y, and z values to and set c be random colors in the RGB spectrum. Inside this class I also created the function display, where I drew an ellipse. I drew the sphere with an array list. In the setup, I created two for loops to create ellipses from the center of the sphere using cos and sin functions. And inside these for loops I added the ellipses created inside the class. In the draw function I created a for loop to display the sun and rotate it in the x-axis.

Screen Shot 2017-05-18 at 9.57.32 AM

Picture 6.

Grabbing and Orbiting: Also inside the class Planet I created two more functions to simulate the grabbing with Leap Motion. The first one is called move. Here, I set the x value equal to handX, the y value to handY, and the z value to 0. I created float called distance, which I equaled to a dist function between x, y, sunX and sunY. SunX and sunY are the variables for the position of the planet which all planet orbit around. I made an if statement so that if the distance between the previously mentioned values is less that 500, the boolean nearSun becomes true and the orbitDistance equals distance. The second function is called orbit. This function is for the rotation of the planets once nearSun becomes true. I made an if statement determining that if nearSun is true and grab is less than 0.8, isOrbiting becomes true. Then created another if statement inside this last one which said that if isOrbiting occurs, x and y equal to a sin and cos function respectively to make the planets rotate around the sun. To grab the planets with Leap Motion, I made another function called intersect so that when the distance between x, y, checkX, checkY,  is less than the corresponding size + 100 isOrbiting is false and if the grab is greater than 0.8 move() occurs. Lastly, I called the function intersect(handX, handY) and orbit inside each of the functions were the planets are created.

Installation:

I cut two triangles of cardboard with the exact angle to show the Pepper’s Ghost effect and covered them with black paper. Then I stuck the triangles to the acrylic using double-sided tape, and used more black paper to cover the back of the acrylic with it. Lastly, I used a cardboard box to place the laptop on top of it so that the view of the user would be more comfortable.

IMG_1722 IMG_1723 IMG_1724FullSizeRender.jpg

Conclusion / Difficulties / Things Learned:

This was a very hard project for me, especially working alone because it had many parts that I did not know how to do. I had higher expectations for my final result of this project and if I had spent more time on it, it would had been better. There were some mistakes that I was not able to resolve before the show. For instance, I ended up not being able to use the Pepper’s Ghost effect for the show because I had to flip the processing sketch in order to reflect it correctly on the acrylic, and when I did so, the grabbing function did not work right.

Also, when people were playing around with my project I realized that the accelerometer was kind of hard to use for people, and that my project was not very intuitive. Although this was a bit disappointing, I learned that I should probably show my projects to my friends before showing them in a show in order to get some feedback from people who do not anything about my project.

----------------------------------------------ARDUINO--------------------------------------------------
x#include <Wire.h>
#include <ADXL345.h>


ADXL345 adxl; //variable adxl is an instance of the ADXL345 library

void setup() {
  Serial.begin(9600);
  adxl.powerOn();

  //set activity/ inactivity thresholds (0-255)
  adxl.setActivityThreshold(75); //62.5mg per increment
  adxl.setInactivityThreshold(75); //62.5mg per increment
  adxl.setTimeInactivity(10); // how many seconds of no activity is inactive?

  //look of activity movement on this axes - 1 == on; 0 == off
  adxl.setActivityX(1);
  adxl.setActivityY(1);
  adxl.setActivityZ(1);

  //look of inactivity movement on this axes - 1 == on; 0 == off
  adxl.setInactivityX(1);
  adxl.setInactivityY(1);
  adxl.setInactivityZ(1);

  //look of tap movement on this axes - 1 == on; 0 == off
  adxl.setTapDetectionOnX(0);
  adxl.setTapDetectionOnY(0);
  adxl.setTapDetectionOnZ(1);

  //set values for what is a tap, and what is a double tap (0-255)
  adxl.setTapThreshold(50); //62.5mg per increment
  adxl.setTapDuration(15); //625us per increment
  adxl.setDoubleTapLatency(80); //1.25ms per increment
  adxl.setDoubleTapWindow(200); //1.25ms per increment

  //set values for what is considered freefall (0-255)
  adxl.setFreeFallThreshold(7); //(5 - 9) recommended - 62.5mg per increment
  adxl.setFreeFallDuration(45); //(20 - 70) recommended - 5ms per increment

  //setting all interrupts to take place on int pin 1
  //I had issues with int pin 2, was unable to reset it
  adxl.setInterruptMapping( ADXL345_INT_SINGLE_TAP_BIT,   ADXL345_INT1_PIN );
  adxl.setInterruptMapping( ADXL345_INT_DOUBLE_TAP_BIT,   ADXL345_INT1_PIN );
  adxl.setInterruptMapping( ADXL345_INT_FREE_FALL_BIT,    ADXL345_INT1_PIN );
  adxl.setInterruptMapping( ADXL345_INT_ACTIVITY_BIT,     ADXL345_INT1_PIN );
  adxl.setInterruptMapping( ADXL345_INT_INACTIVITY_BIT,   ADXL345_INT1_PIN );

  //register interrupt actions - 1 == on; 0 == off
  adxl.setInterrupt( ADXL345_INT_SINGLE_TAP_BIT, 1);
  adxl.setInterrupt( ADXL345_INT_DOUBLE_TAP_BIT, 1);
  adxl.setInterrupt( ADXL345_INT_FREE_FALL_BIT,  1);
  adxl.setInterrupt( ADXL345_INT_ACTIVITY_BIT,   1);
  adxl.setInterrupt( ADXL345_INT_INACTIVITY_BIT, 1);
}

void loop() {

  int x, y, z;
  adxl.readXYZ(&x, &y, &z); //read the accelerometer values and store them in variables  x,y,z

  double xyz[3];
  double ax, ay, az;
  adxl.getAcceleration(xyz);
  ax = xyz[0];
  ay = xyz[1];
  az = xyz[2];

  Serial.print(x);
  Serial.print(",");
  Serial.print(y);
  Serial.print(",");
  Serial.print(z);
  Serial.println();

  delay(100);
}
-----------------------------------------------PROCESSING----------------------------------------------------
import processing.serial.*;
String myString;    //0/1 values in Arduino
Serial myPort;

boolean grabbed = false;


int numOfSensors = 3;
int values[];

float accX, accY, accZ;
float adjX = 0;
float adjY = 0;
float adjZ = 255;
float xOffset, zOffset;
float offsetSpeed = 5;

ArrayList<EllisO> ellipsesO = new ArrayList <EllisO>();
ArrayList<EllisV> ellipsesV = new ArrayList <EllisV>();
float sunX = -500;
float sunY = 0;

Planet half;
Planet stone;
Planet orange;
Planet star;
Planet cone;
Planet cube;

int g = 3; //ring strokeWeight
int rc = 255; //ring color 

int NUM_OF_DUSTS = 500;
Dust[] dusts;


void setup() {
  size(1280, 750, P3D);

  printArray(Serial.list());
  myPort = new Serial(this, Serial.list()[2], 9600); 
  values = new int[numOfSensors];
  myString = myPort.readStringUntil(10);

  leap = new LeapMotion(this);

  dusts = new Dust[NUM_OF_DUSTS];
  half = new Planet(450, 200, 0, 30, 0.065);
  stone = new Planet(600, -50, 0, 50, 0.02);
  //orange = new Planet(200, -150, 0, 2, 0);
  star = new Planet(350, -150, 0, 80, 0.04);
  cone = new Planet(100, -150, 0, 0, 0.06);
  cube = new Planet(300, 250, 0, 5, 0.05);

  for (int i=0; i<NUM_OF_DUSTS; i++) {
    float dx = random(-width, width*2);
    float dy = random( -height, height*2);
    float dz = random(-1200);
    float dsize = random(1, 4);
    dusts[i] = new Dust(dx, dy, dsize, dz);
  }

  //green and orange Ellipse
  for (int angle=0; angle<360; angle 
    += 5*2) {
    float rad = 60;
    float r = cos(radians(angle)) * rad;
    float y = sin(radians(angle)) * rad;
    for (int angleZ=0; angleZ<360; angleZ+=10) {
      float x = cos(radians(angleZ)) * abs(r);
      float z = sin(radians(angleZ)) * abs(r);
      ellipsesO.add( new EllisO(x, y, z));
      ellipsesV.add( new EllisV(x, y, z));
    }
  }
}


void draw() {
  while (myPort.available() > 0) { 
    myString = myPort.readStringUntil(10);
    if (myString != null) {
      String[] data = split(trim(myString), ","); 
      if (data.length == numOfSensors) {
        for (int i = 0; i < data.length; i++) {
          values[i] = int(data[i]);
        }
      }
    }
    accX = float(values[0]) + adjX; //turn data into an integer
    accY = float(values[1]) + adjY;
    accZ = float(values[2]) + adjZ;
  }

  background(0);

  pushMatrix();
  fill(255);
  textSize(18);
  text("Try grabbing the planets on the right and dragging them inside the box", 350, 30, 0);
  popMatrix();

  translate(0, 0, -300);

  if (accX > 50) { //150
    // front
    zOffset-=offsetSpeed;
  } else if (accX < -50) { //-150
    // back
    zOffset+=offsetSpeed;
  }
  if (accY > 50) { //150
    // right
    xOffset+=offsetSpeed;
  } else if (accY < -50) { //-150
    // left
    xOffset-=offsetSpeed;
  }

  leapRun();

  translate(width/2, height/2);
  drawHand();
  translate(xOffset, 0, zOffset);


  xOffset = constrain(xOffset, -1400, 1400);
  zOffset = constrain(zOffset, -1400, 1400);


  //dust
  for (int i=0; i<dusts.length; i++) {
    pushMatrix();
    dusts[i].move();
    dusts[i].display();
    popMatrix();
  }

  // sun
  pushMatrix();
  translate(sunX, sunY, 0);
  pushStyle();
  noFill();
  stroke(255, 20);
  box(800);
  popStyle();

  for (int i=0; i<ellipsesO.size(); i++) {
    EllisO eO = ellipsesO.get(i);
    rotateX(frameCount*0.005);
    eO.display();
  }
  popMatrix();


  // Planets
  stone.displayStone();
  half.displayHalf();
  star.displayStar();
  cone.displayCone();
  cube.displayCube();

  println(frameRate);
  fill(255);

  // sensor values
  //text(accX + " " + accY + " " + accZ, 0, 0);
}

void cone(float bottom, float top, float h, int sides)
{
  pushMatrix();
  
  float angle;
  float[] x = new float[sides+1];
  float[] z = new float[sides+1];
  
  float[] x2 = new float[sides+1];
  float[] z2 = new float[sides+1];
 
 
  for(int i=0; i < x.length; i++){
    angle = TWO_PI / (sides) * i;
    x[i] = sin(angle) * bottom;
    z[i] = cos(angle) * bottom;
  }
  
  for(int i=0; i < x.length; i++){
    angle = TWO_PI / (sides) * i;
    x2[i] = sin(angle) * top;
    z2[i] = cos(angle) * top;
  }
 

  beginShape(TRIANGLE_FAN);
 
  vertex(0,   -h/2,    0);
 
  for(int i=0; i < x.length; i++){
    vertex(x[i], -h/2, z[i]);
  }
 
  endShape();
 
 
  beginShape(QUAD_STRIP); 
 
  for(int i=0; i < x.length; i++){
    vertex(x[i], -h/2, z[i]);
    vertex(x2[i], h/2, z2[i]);
  }
 
  endShape();
 

  beginShape(TRIANGLE_FAN); 
 
  vertex(0,   h/2,    0);
 
  for(int i=0; i < x.length; i++){
    vertex(x2[i], h/2, z2[i]);
  }
 
  endShape();
  
  popMatrix();
}

class Dust {
  float dx, dy, dz, dzOrig;
  float dsize;
  float dclr;
  char keyValue;


  Dust(float _x, float _y, float _size, float _zOrig) {
    dx = _x;
    dy = _y;
    dzOrig = _zOrig;
    dz = _zOrig;
    dsize = _size;
  }


  void display() {
    pushMatrix();
    translate(0, 0, dz);
    fill(255, 255, 0);
    noStroke();
    ellipse(dx, dy, dsize, dsize);
    popMatrix();
  }

  void move() {
    keyValue = key;
    dz++;
    if (dz > 300) {
      dz = dzOrig;
    }
  }
}

class EllisO {
  float x, y, z;
  float size;
  color c;

  EllisO(float _x, float _y, float _z) {
    //void ellisO(int x, int y, int z) 
    x = _x;
    y = _y;
    z = _z;
    size = 6;
    //c = color(227, 89, 34);
    float g = random(150, 255);
    float b = random(0, 53);
    c = color(random(255), random(255), random(255));
    //c = color(255, g, b);
  }

  //////

  void display() {

    pushMatrix();
    translate(x, y, z);

    rotateY(frameCount*0.003);
    pushStyle();
    noStroke();
    fill(c);
    ellipse(0, 0, size, size);
    popStyle();
    popMatrix();
  }

  //void range() {
  //  pushMatrix();
  //  translate(x ,y , z);
  //  pushStyle();
  //  noFill();
  //  stroke(255);
  //  box(400);
  //  popStyle();
  //  popMatrix();
  //}
}

import de.voidplus.leapmotion.*;
LeapMotion leap;
float handX, handY, handZ;
float grab;
PVector[] joints = new PVector[4];

void leapRun() {
  pushStyle();

  for (Hand hand : leap.getHands()) {
    handX = hand.getPosition().x - width/2; //
    handY = hand.getPosition().y- height/2 ; //
    handZ = hand.getPosition().z;

    grab = hand.getGrabStrength();

    for (Finger finger : hand.getFingers()) {


      joints[0] = finger.getPositionOfJointTip();
      joints[1] = finger.getPositionOfJointDip(); // next joint down
      joints[2] = finger.getPositionOfJointPip(); // next to last joint
      joints[3] = finger.getPositionOfJointMcp(); // joint at the bottom of the finger

      noFill();
      if (grab > 0.8) {
        stroke(222, 126, 29);
        strokeWeight(10);
      } else {
        stroke(100); //178, 102, 255
        strokeWeight(5);
      }
      beginShape();
      for (int i=0; i<joints.length; i++) {
        vertex(joints[i].x+xOffset, joints[i].y, joints[i].z + zOffset);
      }
      endShape();
    }
  }
  popStyle();
}

void drawHand() {
  pushMatrix();
  translate(handX+xOffset, handY, zOffset);
  noStroke();
  fill(222, 126, 29);
  if (grab > 0.8) {
    sphere(20);
  } else {
    sphere(10);
  }

  //ellipse(handX, handY, 10,10);
  popMatrix();
}

class Planet {
  float x, y, z;
  float size;
  color c;
  boolean nearSun;
  boolean isOrbiting;
  float orbitDistance = 0;
  float angle;
  float speed;



  Planet(float _x, float _y, float _z, float _size, float _speed) {
    x = _x;
    y = _y;
    z = _z;
    size = _size;
    speed = _speed;
    //angle = _angle;
    //c = color(255);
    nearSun = false;
  }

  void displayHalf() {
    intersect(handX, handY);
    orbit();

    pushMatrix();

    translate(x, y, z);
    noStroke();
    stroke(0);
    strokeWeight(1);
    sphereDetail(15);
    fill(255, 0, 255);
    sphere(size);

    pushMatrix();
    rotateY(frameCount*0.15);
    fill(255);
    ellipse(0, 0, 90, 90);
    popMatrix();

    popMatrix();
  }

  void displayStone() {
    intersect(handX, handY);
    orbit();

    pushMatrix();
    translate(x, y, z);
    for (int angle=0; angle<360; angle+=40) {
      for (int angleY=0; angleY<360; angleY+=100) {
        rotateY(radians(angle));
        rotateX(radians(angleY));
        noStroke();
        fill(random(150, 255));
        box(size);
        delay(1/2);
      }
    }
    popMatrix();
  }

  void displayStar() {
    intersect(handX, handY);
    orbit();

    pushMatrix();
    float spd = frameCount*0.05;
    int size = 80;
    int xe = 0;
    int ye = 0;

    noLights();
    noStroke();
    translate(x, y, z);

    pushMatrix();
    fill(0);
    rotateX(PI/2);
    rotate(spd);
    ellipse(xe, ye, size, size);
    popMatrix();

    pushMatrix();
    fill(0);
    rotateY(PI*3/2);
    rotateY(-spd);
    rotateZ(PI/2);
    ellipse(xe, ye, size, size);
    popMatrix();

    pushMatrix();
    fill(255, 255, 0);
    rotateZ(PI/2);
    rotateX(-spd);
    ellipse(xe, ye, size, size);
    popMatrix();

    pushMatrix();
    fill(255, 255, 0);
    rotateZ(PI/4);
    rotateX(-spd);
    ellipse(xe, ye, size, size);
    popMatrix();

    pushMatrix();
    fill(255, 255, 0);
    rotateZ(-PI/4);
    rotateX(spd);
    ellipse(xe, ye, size, size);
    popMatrix();

    pushMatrix();
    fill(0);
    rotateZ(-PI/4);
    rotateX(PI/2);
    rotateX(spd);
    ellipse(xe, ye, size, size);
    popMatrix();

    pushMatrix();
    fill(0);
    rotateZ(PI/4);
    rotateX(PI/2);
    rotateX(-spd);
    ellipse(xe, ye, size, size);
    popMatrix();

    popMatrix();
  }
  void displayCone() {
    intersect(handX, handY);
    orbit();

    pushMatrix();
    translate(x, y, z);
    stroke(0);
    strokeWeight(1);
    fill(166, 135, 234);
    //fill(76,0,153);
    //fill(51,255,153);
    rotateX(PI/2);
    rotateY(frameCount*0.03);
    cone(1, 30, 400, 40);
    popMatrix();
  }


  void displayCube() {
    intersect(handX, handY);
    orbit();

    int l = 30;
    int d = 10;
    pushMatrix();
    translate(x, y, z);
    rotateY(frameCount*0.02);
    beginShape();
    //for (int disy=-l; disy<l; disy+=d) {
    for (int y=-l; y<l; y+=d) {
      //for (int disx=-l; disx<l; disx+=d) {
      for (int x=-l; x<l; x+=d) {
        for (int z=-l; z<l; z+=d) {
          pushMatrix();
          pushStyle();
          translate(x, y, z);
          stroke(0);
          fill(44, 218, 240);
          box(size);
          popStyle();
          popMatrix();
        }
      }
    }
    endShape();
    popMatrix();
  }

  void move() {
    x = handX;
    y = handY;
    z = 0;

    float distance = dist(x, y, sunX, sunY);
    if (distance < 500) {
      nearSun = true;
      orbitDistance = distance;
    } else {
      nearSun = false;
    }
  }
  void orbit() {
    if (nearSun == true && grab < 0.8) {
      isOrbiting = true;
    }
    if (isOrbiting) {
      float amp = orbitDistance;
      float angle = frameCount * speed;
      x = sunX + cos(angle) * amp; 
      y = sunY + sin(angle) * amp;
    }
  }

  void intersect(float checkX, float checkY) {
    float d = dist(x, y, checkX, checkY); 
    if (d < size+100) {
      isOrbiting = false;
      if (grab > 0.8) {
        move();
      }
    } else {
      // nothing
    }
  }
}

IxLab: Final Project – Dancing Origami (Dan)

Instructor: Dan

Partner: Junyao (June) Wu

Process: For the last project we do for Interaction Lab, we want to create something that represents what we learned from the class, which is not just the coding skills and use of tech gadgets, but the concept of interaction and the IMA spirit of freedom, creativity and fun as well. In the idea presentation, we demonstrated two potential project, Dancing Origami and Managing Seats. The former is a bird that can dance by itself and the latter a system that shows the status of the seats in the library while preventing people occupying the seats for too long. Yet when starting to actually work on the project my partner and I started to have disagreement on which should be our priority: The former is indeed the best suit for what we want to achieve, but we had already tried it once for our midterm projects and gave it up at the last minute because no matter how we adjust the distance the bird just couldn’t move. The latter, on the other hand, is easier to achieve. In fact, it only took one day for us to build a working prototype and to develop a webpage based on that would not be too big a problem. However, not matter how practical the latter is, it neither demonstrates the values we’ve learned throughout the semester, nor is it fun or interactive. Both of us wanted to present what we’ve done with pride, and therefore we chose the first path.

In short, our project, Dancing Origami, is a bird standing on an acrylic board with magnets attached to its feet. There are six electromagnets controlled by Arduino beneath the acrylic board to move the feet of the origami. And through serial communication, processing is used to choreograph the movement of the bird. In other words, we made a dancing bird.

In the process of building the project, however, we encountered more problems than we thought. In the beginning we tried to make a single magnet move between the two electromagnets. The plan was to turn the two electromagnets on and off by turn, hoping that the suction will overcome the fraction and pull the magnet. Yet the magnets we bought were to strong, making it impossible to move it by suction only. Just as we were busy trying different method to lessen the fraction, Dan came and suggested us to use the H Bridge to convert the polarity of the electromagnet so that when we need to move the magnet there will be both suction and repellant. He also brought us magnets that are less strong, which enables the further development of the program. After getting the magnets to move, June folded the origami, attached the magnets to its feet and designed its moves. I coded the Arduino, defined the moves as various functions and got it ready to be called. June then setup the serial communication and coded the processing side, assigning the moves to the keyboards so that we can call the moves by pressing respective keys. Yet when we run it for the first time, we found that the origami kept stepping outside the L shaped regions we designed as the dancefloor. And that’s when June came up with the idea of setting magnets that repel the feet of the origami around the dancefloor to form a shield to keep the feet in place. And, after dozens of trials, we got our first functioning prototype.

WechatIMG7477

WechatIMG7479

The project still has a lot to refine, such as on the processing side there should be an interface indicating the moves of the origami and visualize the use’s choreography. We also got useful feedback from the IMA Show: An elderly man inspired us with the suggestion of “Instead of controlling it to dance, you should be thinking about letting it design its own dance moves.”. A sophomore also suggested to play music that is at the same pace of the dance moves for entertaining purpose. The accuracy also needs to be improved, as the project itself does not reach 100% accuracy. But after all, we made our idea happen, and that’s satisfying enough for me.

I would like to thank my partner June, for the enormous effort, for the outstanding ideas and for tolerating my temper. I would like to pay special thanks to Dan and Rudy, we couldn’t have pull the project without their help. I also want to thank the fellows and the TAs for their generous help.

//Arduino Codes
// don't feel like referencing everytime
int pull1 = 22;
int push1 = 23;
int pull2 = 24;
int push2 = 25;
int pull3 = 29;
int push3 = 28;
int pull4 = 26;
int push4 = 27;
int pull5 = 32;
int push5 = 33;
int pull6 = 31;
int push6 = 30;
//define a pace for the move
int pace = 500;
//a value
int val = 0;
void setup() {
  //set up the pins to output
  pinMode(22, OUTPUT);
  pinMode(23, OUTPUT);
  pinMode(24, OUTPUT);
  pinMode(25, OUTPUT);
  pinMode(26, OUTPUT);
  pinMode(27, OUTPUT);
  pinMode(28, OUTPUT);
  pinMode(29, OUTPUT);
  pinMode(30, OUTPUT);
  pinMode(31, OUTPUT);
  pinMode(32, OUTPUT);
  pinMode(33, OUTPUT);
  //serial communication begin
  Serial.begin(9600);
}

void loop () {
//  pace = 500;
  while (Serial.available()) {

    val = Serial.read();
 
 if (val == 2) {
  move2(pace);
  
 }
 if (val == 3) {
  move3(pace);
  
  
 }
 if (val == 4) {
  move4(pace);
  
 }
 if (val == 5) {
  move5(pace);
  
 }
 if (val == 6) {
  move6(pace);
  
 }
 if (val == 7) {
  move7(pace);
  
 }
 if (val == 1) {
  move1(pace);331222
  
 }
  }
}
//Moves
//define a starting&ending state
void move0 () {
  digitalWrite(pull1,HIGH);
  digitalWrite(push1,LOW);
  digitalWrite(pull2,LOW);
  digitalWrite(push2,LOW);
  digitalWrite(pull3,LOW);
  digitalWrite(push3,LOW);
  digitalWrite(pull4,HIGH);
  digitalWrite(push4,LOW);
  digitalWrite(pull5,LOW);
  digitalWrite(push5,LOW);
  digitalWrite(pull6,LOW);
  digitalWrite(push6,LOW);
}


void move1 (int pace) {
  move0();
  digitalWrite(push6,HIGH);
  digitalWrite(pull4,LOW);
  digitalWrite(push4,HIGH);
  digitalWrite(pull5,HIGH);
  digitalWrite(push5,LOW);
  delay(pace);
  digitalWrite(pull4,HIGH);
  digitalWrite(push4,LOW);
  digitalWrite(pull5,LOW);
  digitalWrite(push5,HIGH);
  delay(pace);
  digitalWrite(pull4,LOW);
  digitalWrite(push4,HIGH);
  digitalWrite(pull5,HIGH);
  digitalWrite(push5,LOW);
  delay(pace);
  digitalWrite(pull4,HIGH);
  digitalWrite(push4,LOW);
  digitalWrite(pull5,LOW);
  digitalWrite(push5,HIGH);
  move0();
  delay(pace);
}


void move2 (int pace) {
  move0();
  digitalWrite(push3,HIGH);
  digitalWrite(pull1,LOW);
  digitalWrite(push1,HIGH);
  digitalWrite(pull2,HIGH);
  digitalWrite(push2,LOW);
  delay(pace);
  digitalWrite(pull1,HIGH);
  digitalWrite(push1,LOW);
  digitalWrite(pull2,LOW);
  digitalWrite(push2,HIGH);
  delay(pace);
  digitalWrite(pull1,LOW);
  digitalWrite(push1,HIGH);
  digitalWrite(pull2,HIGH);
  digitalWrite(push2,LOW);
  delay(pace);
  digitalWrite(pull1,HIGH);
  digitalWrite(push1,LOW);
  digitalWrite(pull2,LOW);
  digitalWrite(push2,HIGH);
  move0();
  delay(pace);
  
}


void move3(int pace){
  move0();
  digitalWrite(push5,HIGH);
  digitalWrite(pull4,LOW);
  digitalWrite(push4,HIGH);
  digitalWrite(pull6,HIGH);
  digitalWrite(push6,LOW);
  delay(pace);
  digitalWrite(pull4,HIGH);
  digitalWrite(push4,LOW);
  digitalWrite(pull6,LOW);
  digitalWrite(push6,HIGH);
  delay(pace);
  digitalWrite(pull4,LOW);
  digitalWrite(push4,HIGH);
  digitalWrite(pull6,HIGH);
  digitalWrite(push6,LOW);
  delay(pace);
  digitalWrite(pull4,HIGH);
  digitalWrite(push4,LOW);
  digitalWrite(pull6,LOW);
  digitalWrite(push6,HIGH);
  move0();
  delay(pace);
}


void move4 (int pace) {
   move0();
  digitalWrite(push2,HIGH);
  digitalWrite(pull1,LOW);
  digitalWrite(push1,HIGH);
  digitalWrite(pull3,HIGH);
  digitalWrite(push3,LOW);
  delay(pace);
  digitalWrite(pull1,HIGH);
  digitalWrite(push1,LOW);
  digitalWrite(pull3,LOW);
  digitalWrite(push3,HIGH);
  delay(pace);
  digitalWrite(pull1,LOW);
  digitalWrite(push1,HIGH);
  digitalWrite(pull3,HIGH);
  digitalWrite(push3,LOW);
  delay(pace);
  digitalWrite(pull1,HIGH);
  digitalWrite(push1,LOW);
  digitalWrite(pull3,LOW);
  digitalWrite(push3,HIGH);
  move0();
  delay(pace);
}


void move5(int pace) {
  move0;
  digitalWrite(push6,HIGH);
  digitalWrite(pull4,LOW);
  digitalWrite(push4,HIGH);
  digitalWrite(pull5,HIGH);
  digitalWrite(push5,LOW);
  delay(pace);
  digitalWrite(pull4,HIGH);
  digitalWrite(push4,LOW);
  digitalWrite(pull5,LOW);
  digitalWrite(push5,HIGH);
  //move0();
  delay(pace);
  digitalWrite(push3,HIGH);
  digitalWrite(pull1,LOW);
  digitalWrite(push1,HIGH);
  digitalWrite(pull2,HIGH);
  digitalWrite(push2,LOW);
  delay(pace);
  digitalWrite(pull1,HIGH);
  digitalWrite(push1,LOW);
  digitalWrite(pull2,LOW);
  digitalWrite(push2,HIGH);
  delay(pace);
  //move0();
  digitalWrite(push6,HIGH);
  digitalWrite(pull4,LOW);
  digitalWrite(push4,HIGH);
  digitalWrite(pull5,HIGH);
  digitalWrite(push5,LOW);
  delay(pace);
  digitalWrite(pull4,HIGH);
  digitalWrite(push4,LOW);
  digitalWrite(pull5,LOW);
  digitalWrite(push5,HIGH);
  //move0();
  delay(pace);
  digitalWrite(push3,HIGH);
  digitalWrite(pull1,LOW);
  digitalWrite(push1,HIGH);
  digitalWrite(pull2,HIGH);
  digitalWrite(push2,LOW);
  delay(pace);
  digitalWrite(pull1,HIGH);
  digitalWrite(push1,LOW);
  digitalWrite(pull2,LOW);
  digitalWrite(push2,HIGH);
  move0();
  delay(pace);
}


void move6(int pace) {
  move0();
  digitalWrite(push5,HIGH);
  digitalWrite(pull4,LOW);
  digitalWrite(push4,HIGH);
  digitalWrite(pull6,HIGH);
  digitalWrite(push6,LOW);
  delay(pace);
  digitalWrite(pull4,HIGH);
  digitalWrite(push4,LOW);
  digitalWrite(pull6,LOW);
  digitalWrite(push6,HIGH);
  delay(pace);
  digitalWrite(push2,HIGH);
  digitalWrite(pull1,LOW);
  digitalWrite(push1,HIGH);
  digitalWrite(pull3,HIGH);
  digitalWrite(push3,LOW);
  delay(pace);
  digitalWrite(pull1,HIGH);
  digitalWrite(push1,LOW);
  digitalWrite(pull3,LOW);
  digitalWrite(push3,HIGH);
  delay(pace);
  digitalWrite(push5,HIGH);
  digitalWrite(pull4,LOW);
  digitalWrite(push4,HIGH);
  digitalWrite(pull6,HIGH);
  digitalWrite(push6,LOW);
  delay(pace);
  digitalWrite(pull4,HIGH);
  digitalWrite(push4,LOW);
  digitalWrite(pull6,LOW);
  digitalWrite(push6,HIGH);
  delay(pace);
  digitalWrite(push2,HIGH);
  digitalWrite(pull1,LOW);
  digitalWrite(push1,HIGH);
  digitalWrite(pull3,HIGH);
  digitalWrite(push3,LOW);
  delay(pace);
  digitalWrite(pull1,HIGH);
  digitalWrite(push1,LOW);
  digitalWrite(pull3,LOW);
  digitalWrite(push3,HIGH);
  move0();
  delay(pace);
}


void move7 (int pace) {
  move1(pace);
  move1(pace);
  move2(pace);
  move2(pace);
  move3(pace);
  move3(pace);
  move4(pace);
  move4(pace);
  move5(pace);
  move5(pace);
  move6(pace);
  move6(pace);
}

//Processing Codes
import processing.serial.*; 
Serial myPort; 
int val; 
void setup() { 
  printArray(Serial.list());
  // this prints out the list of all 
     // available serial ports on your computer.

  myPort = new Serial(this, Serial.list()[3], 9600); 
// Change the Serial.list()[0] to your port
  } 
void draw() { 
  // to send a value to the Arduino 
  //if (keyPressed) { 
  //  myPort.write(key); 
  //  print(key);1
  //  }
  if (keyPressed){
  myPort.write(val);
  } 
}
  void keyPressed(){
  if(key == '1'){
  val = 1;
  }
  if(key == '2'){
  val = 2;
  }
  if(key == '3'){
  val = 3;
  }
  if(key == '4'){
  val = 4;
  }
  if(key == '5'){
  val = 5;
  }
  if(key == '6'){
  val = 6;
  }
  if(key == '7'){
  val = 7;
  }
  
  }
  
  

IxLab: Lab12 – Media Controller (Dan)

Date: May 5th, 2017

Instructor: Dan

Partner: Junyao (June) Wu

Aim: In this lab, we were instructed to use a controller made with Arduino to control a processing-sketch-controlled images audio or video.

Process: It would be quite easy to use a distance sensor to control the playing and stopping of the video. Yet we had played around with it too many times and thus decided to use something else to achieve such goal by using the tilt switch.

We then made a skateboard and attached the tilt switch onto it. To figure out the mechanism of the tilt switch we ran a few test and found out that we could use it like using a button. Only to note that the state of the switch will be deemed high only when the switch is tilted.

Then we set up the serial communication and the video library on the processing end, and chose a video of skateboarding as the sample so that whenever the skate board is tilted (in reality the skateboard is stopped), the video will be stoped.WechatIMG7476

//Arduino Code
const int buttonPin = 3;
const int ledPin = 13;
int buttonState = 0;
void setup() {

  // initialize the pushbutton pin as an input:
  pinMode(buttonPin, INPUT);
  pinMode(ledPin,OUTPUT);
  Serial.begin(9600); //start the serial communication
}

void loop() {
  // read the state of the pushbutton value:
  buttonState = digitalRead(buttonPin);


  // check if the pushbutton is pressed.
  // if it is, the buttonState is HIGH:
  if (buttonState == HIGH) {
    Serial.write(1);
    digitalWrite(ledPin,HIGH);
  } else {
Serial.write(0);
digitalWrite(ledPin,LOW);
  }
}
//Processing Code
import processing.video.*;
Movie myMovie; 
import processing.serial.*; 
Serial myPort; 
void setup() {
 printArray(Serial.list());
  // this prints out the list of all 
     // available serial ports on your computer.

  myPort = new Serial(this, Serial.list()[2], 9600); 
// Change the Serial.list()[0] to your port
size(1000, 600);

myMovie = new Movie(this, "a.mp4" );

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

void draw() {
  println(myPort.read());
image(myMovie, 0, 0, width, height);
if (myPort.read() == 1){
  myMovie.pause();
 println();
} else{ 
  myMovie.play();
}
}

IxLab: Lab11 – Drawing Machines (Dan)

Date: April 28th, 2017

Instructor: Dan

Partner: Junan (Owen) Zhang     Junyao (June) Wu

Aim: For today’s lab, we are going to work together not just within the two of us but also with another group to create a working prototype of a drawing machine.

Process: It was my first time working with Owen, and it was a pleasant experience. We quickly collected the materials and started assembling. The first obstacle is how to use the nail. It is because we never used a nail like this and it is until later did we find out that we have to split the legs of it to fix it. The second one is that we put the direction of the blue board wrong, resulting in that when we tried to put our work with the other group, it was impossible to use a nail to put them in place.

Other than that there’s no obstacle ahead. Together with the other group we fixed the branches of the drawing machines together and turned it on. Along with the buzzing it started to draw circles and with the change of code it starts to show features of “steeping”.


/*
 Stepper Motor Control - one revolution

 This program drives a unipolar or bipolar stepper motor.
 The motor is attached to digital pins 8 - 11 of the Arduino.

 The motor should revolve one revolution in one direction, then
 one revolution in the other direction.


 Created 11 Mar. 2007
 Modified 30 Nov. 2009
 by Tom Igoe

 */

#include <Stepper.h>

const int stepsPerRevolution = 200;  // change this to fit the number of steps per revolution
// for your motor

// initialize the stepper library on pins 8 through 11:
Stepper myStepper(stepsPerRevolution, 8, 9, 10, 11);

void setup() {
  // set the speed at 60 rpm:
  myStepper.setSpeed(60);
  // initialize the serial port:
  Serial.begin(9600);
}

void loop() {
  // step one revolution  in one direction:
  Serial.println("clockwise");
  myStepper.step(stepsPerRevolution);
  delay(500);

  // step one revolution in the other direction:
  Serial.println("counterclockwise");
  myStepper.step(-stepsPerRevolution);
  delay(500);
}

Final Project

Due Date : May 18

Instructor : Daniel

For the Final Project, I was assigned to create an interactive system of your choice using Processing and Arduino. I wanted to make an entertaining game that reflects life in Shanghai. As I like shooting game, I decided to make a first-person view shooting arcade game engaging the physical part as game controlling. Thinking about the target to shoot, I could think of smog which is a serious issue in the city, and set balloons with the masks hung on it as the target.

The most challenging and time consuming part was to figure out how to track the gun and how to implement first person view. First I considered using angle sensor and distance sensor as options, but player would not feel comfortable and feel like playing the arcade game with the fixed gun with a lot of limitations controlling it. Getting some advice from the professor and other classmates, I decided to use color tracking with web cam; color of the gun would be tracked and that tracked position control the view of the screen. I went to the the Computer Vision workshop held by professor Moon learned how the color tracking works. I wanted to apply it to track the position of the color, but there were a lot of problems find the exact position of the object I want to track because of the lighting and different angle affects the color the web cam recognizes. So I did a lot of research to do efficient object tracking, tried different kinds of libraries of color and blob tracking, and ended up using the codes from the multiple color tracking that reflect both the hsb and rgb features in the openCV library.

For displaying the game screen as a first person perspective, I considered different options such as using 3D background, using camera() function in the processing, etc. But as I was not familiar with these options, I just decided to control the location of the image displayed. This means Processing is just reprinting the image at the different location as the gun moves.

Then I started to make game screen, which has three kinds of background – day, night, and twilight, and added random colored balloons with masks popping up in random timing. Then I set the timer and score function, and displayed it at the top of the game screen. Followings are the screenshots of the game screen.

s3s1

s2

For the Arduino part, push button became a trigger of the gun, so that if I push the button, I can fire the gun. Serial communication was used to send the input from the Arduino to Processing. For the physical part of the gun, I used Tinkercad to design the 3D model of the gun.tkcad

I made a hole in the trigger part to put the breadboard in it, but when I actually printed it out, the hole was smaller than I expected. As a result, I could not put the breadboard with the push button inside the trigger part, so I just attached it outside as the picture below. Also, in order to keep the physical part as simple as I can, I used INPUT_PULLUP in Arduino code and did no use resistor instead of just INPUT and using resistor.

g

<Arduino Code for Push Button>c

 

 

Unless the player is wearing purple shirt, tracking the gun worked very well as the following video.

Then I created different kinds of screen such as initial screen, setting screen, counting screen, and result screen, etc to make the game more organized. Before each round of the game starts, the user will be asked to set the color of the gun in the setting screen by clicking the gun on the web cam. Also, in case the gun is not properly tracked, user can always go back to the setting screen during the game by pressing the ‘s’ key on the keyboard.

Here is the demo of the game.

smogdemo

Doing the project, I could explore a lot of different things from openCV to 3D modeling. This project was a lot of coding, and I could figure out how to efficiently dealing with the codes spending time on debugging. However, there is also limitation that I could not improve loading speed of the game. It seemed like there are so many things happening in processing as many images are loaded and relocated every milli seconds. Moreover, it took at least 7 seconds after the game started for serial communication to work so that I can shoot. I should attempt using different functions in Processing to load images instead of PImage for speed up, and care more about the delicacy of the physical part.

 

import processing.serial.*;
import gab.opencv.*;
import processing.video.*;
import java.awt.Rectangle;

Serial myPort;
int val;

int cameraX, cameraY;
int screen = 0; //init 0, setting 1, game 2, counting 3, result 4, smog 10
int scene = 1; //night 1, day 2, twilight 3

PImage chSmog;

void setup() {
  size(1400, 800, P3D);
  setupSerial();
  color_setup();
  bg_setup();
  balloons_setup();
  scoring_setup();
  countingSetup();
  chSmog = loadImage("chsmog.png");
}

void draw() {
  //updateSerial();
  if (screen == 1 || screen == 2 ){
    colortrack();
  }
  if (screen == 0){
    initScreen();
  }else if (screen == 1){
    settingScreen();
  }else if (screen == 2){
    gameScreen(scene);
  }else if (screen == 3){
    countingScreen();
  }else if (screen == 4){
    resultScreen();
  }else if (screen == 10){
    smogScreen();
  }
}

void play(){
  screen = 2;
}

void smog_attack(){
  screen = 10;
}

void count_masks(){
  screen = 3;
}

void showResult(){
  screen = 4;
}

void restart(){
  level = 1;
  scene = 1;
  needs = 4;
  score = 0;
  seconds = 45;
  lastAddTime = 0;
  balloons.clear();
  screen = 1;
}

void mousePressed() {
  if (screen == 0){
    screen = 1;
  }else if (screen == 1){
    color c = get(mouseX, mouseY);
    println("r: " + red(c) + " g: " + green(c) + " b: " + blue(c));     
    int hue = int(map(hue(c), 0, 255, 0, 180));      
    colors[0] = c;
    hues[0] = hue;      
    println("color index " + (0) + ", value: " + hue);
    startMillis = millis()/1000;
    ready = startMillis + 19;
    sx = 0;
    play();
  }
}

void keyPressed() {
  if (screen == 0){
    if (key == 'r'){screen = 1;}
  }
  if (key == 's') {
    screen = 1; 
  }
}

//Balloons
PImage redb, greenb, pinkb;
PImage shoot;
ArrayList<int[]> balloons = new ArrayList<int[]>();
int interval = round(random(2000,4000));
float lastAddTime=0; 
boolean shooting = false;
int gx, gy;
float bSpeed = 1;

void balloons_setup(){
  redb = loadImage("redb.png");
  greenb = loadImage("greenb.png");
  pinkb = loadImage("pinkb.png");
  redb.resize(100,250);
  greenb.resize(130,250);
  pinkb.resize(125,250);
  shoot = loadImage("fire1.png");
  shoot.resize(390,280);
}

void Balloons(){
  balloonAdder();
  balloonHandler();
}

void balloonHandler(){
  for (int i = 0; i < balloons.size(); i++){
    balloonDrawer(i);
    balloonFloating(i);
    balloonRemover(i);   
  }
}

void balloonAdder(){
  if (millis()-lastAddTime > interval){
    int x = int(random(1600)) - 900;
    int y = 200 + int(random(400));
    int rcolor = int(random(3));
    int[] randB = {x,y,rcolor};
    balloons.add(randB);
    lastAddTime = millis();    
  }
}


void balloonDrawer(int index){
  int[] balloon = balloons.get(index);
  PImage bcolor;
  if (balloon[2] == 0){
    bcolor = redb;
  } else if (balloon[2] == 1){
    bcolor = greenb;
  } else{
    bcolor = pinkb;
  }
  image(bcolor, balloon[0], balloon[1]);
}

void balloonFloating(int index){
  int[] balloon = balloons.get(index);
  balloon[1] -= bSpeed;  
}

void balloonRemover(int index){
  gx = width/2-cameraX-150;
  gy = height/2-cameraY;
  shooting = shoot();
  int[] balloon = balloons.get(index);
  if (balloon[1] < -800){
    balloons.remove(index);
    score();
  } else if (shooting
        && (gx > balloon[0]-50 && gx < balloon[0]+50) &&
          (gy > balloon[1]-50 && gy < balloon[1]+50)){
     balloons.remove(index);
     score();
  }
}

boolean shoot(){
  updateSerial();
  if (val == 0 || mousePressed){
    image(shoot,width/2-cameraX-220,height/2-cameraY-20);
    return true;
  }
  return false;
}

void setupSerial(){
  String portName = Serial.list()[1];
  myPort = new Serial(this, portName, 9600);
}

void updateSerial(){
  if (myPort.available() >0){
    val = myPort.read();
  }
  println(val);
}

//Draw Background
PImage night;
PImage gun;
PImage day;
PImage twilight;
PImage title;
PImage shanghai;
PImage smoke;
PImage start;
PImage next;
PImage restart;

void bg_setup(){
  title = loadImage("smog.png");
  shanghai = loadImage("shanghai.png");
  smoke = loadImage("smoke.png");
  start = loadImage("start.png");
  next = loadImage("continue.png");
  restart = loadImage("restart.png");
  night = loadImage("night.jpg");
  gun = loadImage("gun1.png");
  gun.resize(550,450);
  day = loadImage("Shanghai-skyline-panoramic-water.jpg");
  twilight = loadImage("twilight.jpeg");
}

void outline_night(){
  fill(0);
  noStroke();
  rect(0-cameraX,0-cameraY,1400,50);
  rect(0-cameraX,750-cameraY,1400,50);
  rect(0-cameraX,0-cameraY,100,800);
  rect(1300-cameraX,0-cameraY,100,800);
}

void outline_day(){
  fill(0);
  noStroke();
  rect(0-cameraX,0-cameraY,1400,50);
  rect(0-cameraX,750-cameraY,1400,50);
  rect(0-cameraX,0-cameraY,20,800);
  rect(1380-cameraX,0-cameraY,20,800);
}

void bg_night(){
  translate(cameraX,cameraY);
  night.resize(2800,1600);
  image(night,-1300,-750);  
}

void bg_day(){
  translate(cameraX,cameraY);
  day.resize(2800,1600);
  image(day,-1380,-750); 
}

void bg_twilight(){
  translate(cameraX,cameraY);
  twilight.resize(2800,1600);
  image(twilight,-1380,-750);
}

//Scoring
PImage clock; 
PImage mask;
PImage timesup;
PFont font;
int score = 0;
float seconds;  
float startTime = 45;
float ready;
float startMillis = 0;
int timerWidth = 200;
int level = 1;
int needs = 4;

void scoring_setup(){
  clock = loadImage("clock.png");
  mask = loadImage("mask.gif");
  clock.resize(40,40);
  mask.resize(40,40);
  timesup = loadImage("timesup.png");
  timesup.resize(450,220);
  
}

void score(){
  score++;
}

void printScore(){
  image(mask, 120-cameraX, 5-cameraY);
  font = loadFont("YuppyTC-Regular-25.vlw");
  textFont(font,32);
  textAlign(CENTER);
  fill(255);
  textSize(25);
  text(score, 185 - cameraX, 35 - cameraY);
  text("Level : "+level, 580 - cameraX, 35 - cameraY);
}

void timer(){
  seconds = startTime + startMillis - millis()/1000;
  
  if (seconds<0){
    smog_attack();
  }
}

void smogAttack(){
  screen = 10;
}

void drawTimer() {
  image(clock, 260-cameraX, 5-cameraY);
  noStroke();
  fill(189, 195, 199);
  rectMode(CORNER);
  rect(305-cameraX, 25 - cameraY, timerWidth, 10);
  if (seconds > 15 && seconds < startTime + startMillis -5) {
    fill(46, 204, 113);
  } else if (seconds > 5) {
    fill(230, 126, 34);
  } else if (seconds >0){
    fill(231, 76, 60);
  }
  if (seconds < startTime + startMillis -10){
    rectMode(CORNER);
    rect(305-cameraX, 25 - cameraY, timerWidth*(seconds/startTime), 10);
  }
  if (ready < seconds) {
    textAlign(CENTER);
    fill(255);
    textSize(50);
    text("Please save people in Shanghai from smog!nCollect the masks by shooting balloons", width/2-cameraX, height/2-cameraY);
  }
}

void levelup(){
  level++;
  scene = level % 3;
  needs += 2;
  bSpeed += 0.3;
  score = 0;
  seconds = 45;
  lastAddTime = 0;
  balloons.clear();
  screen = 1;
}

//Screens
PImage maskOn;
PImage crying;
PImage ss;
PImage smogattack;
PImage overbg;
int s = 0;
int f = 0;
int count = 0;
int count2 = 0;
int sx = 100;


void initScreen(){
  image(shanghai,0,height-600);
  smoke.resize(1100,400);
  image(smoke, 200,0);
  title.resize(650,320);
  image(title,width/2-325,height/2-260);
  start.resize(200,100);
  image(start,100,400);
}

void settingScreen(){
  if (video.available()) {
      video.read();
  }
  chSmog.resize(450,335);
  image(chSmog,width/2 + 100,100);
  textAlign(CENTER);
  fill(0);
  textSize(30);
  text("Click the gun to be tracked", width/2, height/2 + 230);
}


void gameScreen(int scene){
  if (scene == 1){
    bg_night();
    Balloons();
    outline_night();
  }else if(scene == 2){
    bg_day();
    Balloons();
    outline_day();
  }else {
    bg_twilight();
    Balloons();
    outline_day();
  }
  image(gun,width/2-cameraX-150,height/2-cameraY);
  printScore();
  timer();
  drawTimer();
  textFont(font,32);
  textAlign(CENTER);
  fill(255);
  textSize(15);
  text("Press 's' to reset the gun", 100-cameraX, height-30-cameraY);
  
}

void smogScreen(){
  smogattack = loadImage("smogattack.png");
  //smogattack.resize(1000, 600);
  if (scene == 1){
    overbg = loadImage("night.jpg");
    outline_night();
  }else if(scene == 2){
    overbg = loadImage("Shanghai-skyline-panoramic-water.jpg");
    outline_day();
  }else {
    overbg = loadImage("twilight.jpeg");
    outline_day();
  }
  image(overbg,0,0);
  image(smogattack, sx, 100);
  image(timesup, width/2-200, height/2-100);
  sx += 25;
  if (sx > 150){
    count_masks();
  }
}

void countingSetup(){
  maskOn = loadImage("gotmask.png");
  crying = loadImage("crying.png");
  ss = loadImage("smog_shanghai.jpg");
  ss.resize(1400,800);
  maskOn.resize(180,190);
  crying.resize(125, 250);
}

void countingScreen(){
  s = score;
  if (score >= needs) {
    s = needs;
  }else{
    f = needs - score;
  }
  image(ss,0,0);
  whoGotMasks();
  if (count > s){
  noMasks();}
  if (count2 > f){
    count = 0;
    count2 = 0;
    screen = 4;
    delay(1000);
  } 
}

void whoGotMasks(){
  if (count <= s){
    for (int i = 0; i < count; i++){
      if(i<=5) {image(maskOn, i*180+100, 100);}
      else {image(maskOn, (i-6)*180+100, 400);}
    }
  }
  count++;
  delay(500);
}

void noMasks(){
  if (count2 < f){
    for (int i = 0; i < count2+1; i++){
      image(crying, i*125+100, 400);
    }
  }
  count2++;
  delay(300);
}

void resultScreen(){
  fill(0);
  rect(0,0,1400,800);
  textFont(font,32);
  textAlign(CENTER);
  fill(255);
  textSize(50);
  text("Level : "+level+"nMasks Collected : "+score, 700, 100);
  if(score >= needs){
    text("Congratulations!nYou saved everyone in Shanghai! nLEVEL UP!",700,350);
    image(next, 575, 540);
    if (mousePressed){levelup();}    
  }else{
    text("More masks needed.. nGAME OVER",700,350);
    restart.resize(240,80);
    image(restart, 575, 540);
    if (mousePressed){restart();}
  }
}

//Color Tracking

Capture video;
OpenCV opencv;
PImage src;
ArrayList<Contour> contours;

// <1> Set the range of Hue values for our filter
//ArrayList<Integer> colors;
int maxColors = 4;
int[] hues;
int[] colors;
int rangeWidth = 10;

PImage[] outputs;

void color_setup(){
  video = new Capture(this, 640, 480);
  opencv = new OpenCV(this, 640, 480);
  contours = new ArrayList<Contour>();
  
  // Array for detection colors
  colors = new int[maxColors];
  hues = new int[maxColors];
  
  outputs = new PImage[maxColors];
  
  video.start();
}

void colortrack(){
  background(150);
  
  if (video.available()) {
    video.read();
  }

  // <2> Load the new frame of our movie in to OpenCV
  opencv.loadImage(video);
  
  // Tell OpenCV to use color information
  opencv.useColor();
  src = opencv.getSnapshot();
  
  // <3> Tell OpenCV to work in HSV color space.
  opencv.useColor(HSB);
  
  detectColors();
  
  // Show images
  image(src, 0, 0);
  for (int i=0; i<outputs.length; i++) {
    if (outputs[i] != null) {
      image(outputs[i], width-src.width/4, i*src.height/4, src.width/4, src.height/4);
      
      noStroke();
      fill(colors[i]);
      rect(src.width, i*src.height/4, 30, src.height/4);
    }
  }
  
  // Print text if new color expected
  textSize(20);
  stroke(255);
  fill(255);
  
  displayContoursBoundingBoxes();
}

void detectColors() {
    
  for (int i=0; i<hues.length; i++) {
    
    if (hues[i] <= 0) continue;
    
    opencv.loadImage(src);
    opencv.useColor(HSB);
    
    // <4> Copy the Hue channel of our image into 
    //     the gray channel, which we process.
    opencv.setGray(opencv.getH().clone());
    
    int hueToDetect = hues[i];
    //println("index " + i + " - hue to detect: " + hueToDetect);
    
    // <5> Filter the image based on the range of 
    //     hue values that match the object we want to track.
    opencv.inRange(hueToDetect-rangeWidth/2, hueToDetect+rangeWidth/2);
    
    //opencv.dilate();
    opencv.erode();
    
    // TO DO:
    // Add here some image filtering to detect blobs better
    
    // <6> Save the processed image for reference.
    outputs[i] = opencv.getSnapshot();
  }
  
  // <7> Find contours in our range image.
  //     Passing 'true' sorts them by descending area.
  if (outputs[0] != null) {
    
    opencv.loadImage(outputs[0]);
    contours = opencv.findContours(true,true);
  }
}

void displayContoursBoundingBoxes() {
  
  for (int i=0; i<contours.size(); i++) {
    
    Contour contour = contours.get(i);
    Rectangle r = contour.getBoundingBox();
    
    if (r.width < 40 || r.height < 40)
      continue;
    
    //stroke(255, 0, 0);
    //fill(255, 0, 0, 150);
    //strokeWeight(2);
    //rect(r.x, r.y, r.width, r.height);
    cameraX = r.x*1400/640;
    if (r.y > 310){
      cameraY = height-310*800/280;
    }else{
    cameraY = height-r.y*800/280;
  }
  }
}

Interaction Lab Final Project, Patricia Wier; Prof Dan

Patricia Wier

Professor Daniel Mikesell

Concept: A true/false quiz that discovers your spirit animal.

To begin, I started by making the structure for the quiz in processing. I made a start page, 10 question pages, and 11 result pages. I created the start, the true and the false buttons with a class. These buttons allow you to move from page to page. Furthermore, I added a point system, so if you choose true you get 1 point (if you choose false you get 0 points). The results appear based on how many points you get.  The reason I have 11 results is because of the chance of someone getting 0 points.

After making the structure, I added text on all the pages. I put in the questions and results. Then I added pictures to those pages. I also added sound to the title page, so when you begin the page you hear the sound.

 

Questions:

  1. You Prefer the Ocean to a Forest.
  2. Cats are Definitely Better than Dogs
  3. You are Super Groovy
  4. In Your Opinion the Square Root of 642 is 27.4332
  5. You are a Russian Spy
  6. Newton’s Third Law is Your Favorite
  7. Your Biggest Fear is Slow Internet (and Zombies)
  8. Patricia is the Funniest Person You Know. #ClassClown2020
  9. Yao Ming over Lebron
  10. Google Drive is the Best!

Results (points)

  1. Magical Musk Deer
  2. Infectious Proteobacteria
  3. Simple (but cute) Kitten
  4. Bothersome Bumble Bee
  5. Meddlesome Mongoose
  6. Guarded Armadillo
  7. Lonely Whale
  8. God
  9. Pensive Pencil Sharpener
  10. Representative Qilin
  11. Baritone Bear

 

Arduino Trouble:

Then I tried to add Arduino into my project. My goal was to have the true/false buttons currently controlled by pressing the mouse, to be controlled by two different Arduino sensors.

I made the circuit for the Arduino sensors and I was able to connect the processing and Arduino programs through serial communication, but getting the sensor to control the buttons and/or work in the same way as pressing the buttons with the mouse was challenging. I tried many times commenting out certain parts of the mouse code and attempted to replace it with Arduino controls but the system kept crashing and/or generally not working.

18674880_1851544521526125_2113329571_o

 

 

Processing Code without Arduino:
import processing.sound.*;
boolean soundisplaying = false;
//intro sound
SoundFile spirit2;

//questions
PImage img1;
PImage cat;
PImage groovy;
PImage math;
PImage russian;
PImage newton;
PImage slow;
PImage clown;
PImage yao;
PImage drive;


//results
PImage deer;
PImage bacteria;
PImage kitten;
PImage bee;
PImage mongoose;
PImage armadillo;
PImage whale;
PImage god;
PImage sharpener;
PImage qilin;
PImage bear;

static final int MAX = 2, GAP = 50, DIM = 120, RAD = DIM >> 1;
int score;
int page, cx, cy;

 
Button next;
Button yes;
Button no;
 
void setup() {
  size(900, 700);
  //intro sound
  spirit2 = new SoundFile(this, "spirit2.wav");
  
  frameRate(50);
  smooth();
 
  rectMode(CORNER);
  textAlign(CENTER, CENTER);
 
  stroke(0);
  strokeWeight(1.5);
 
  cx = width  >> 1;
  cy = height >> 1;
 
  next = new Button("START", width - Button.W - GAP, height - Button.H - GAP);
  yes = new Button ("TRUE", width/3 - Button.W-GAP, height - Button.H - GAP);
  no = new Button ("FALSE", width - Button.W-GAP, height - Button.H - GAP);
 
 //questions
  img1 =loadImage ("ocean.jpg");
  cat =loadImage ("cat.jpg");
  groovy =loadImage("groovy.jpg");
  math =loadImage("math.jpg");
  russian =loadImage("russia.jpg");
  newton =loadImage("newton.jpg");
  slow =loadImage("slow.jpg");
  clown =loadImage ("clown.jpg");
  yao =loadImage("yao.jpg");
  drive =loadImage("drive.jpg");
  
  //results
  deer =loadImage("muskdeer.jpg");
  bacteria =loadImage("bacteria.jpg");
  kitten =loadImage("kitten.jpg");
  bee =loadImage("bee.jpg");
  mongoose =loadImage ("mongoose.jpg");
  armadillo =loadImage ("armadillo.jpg");
  whale =loadImage ("whale.jpg");
  god =loadImage ("god.jpg");
  sharpener =loadImage ("sharpener.jpg");
  qilin =loadImage ("qilin.jpg");
  bear =loadImage ("bear.jpg");
  

}
 
void draw() {
  background(120,0,150);
   textSize(Button.TXTSZ);

 
  if (page==0) next.display();
  if (page>0 && page<12) yes.display(); 
  if (page>0 && page<12) no.display();
 
  pageSelector();        
}

void mousePressed() {
  if (page < MAX && next.isHovering)  ++page;
 if (yes.isHovering) {
    ++page;
    ++score;
  }
 if (no.isHovering) {
    ++page; 
  }
  redraw();
}
 
void mouseMoved() {
 
 next.isInside();
 yes.isInside();
 no.isInside();
 
 redraw();
}

 
void page0() { //title page
  fill(150,50,60);
  textSize(0100);
  fill(0,0,0);
  text("What's Your Spirit Animal?", cx, cy);
  if (soundisplaying ==false) {
      spirit2.play();
      soundisplaying = true; }
}
 /*void page1() {
    fill(150,50,60);
  textSize(0100);
  fill(0,0,0);
  text("Question 1?", cx, cy);
  
}*/
 void page2() {
    fill(0,0,160);
  textSize(40);
  fill(0,0,0);
  text("      You Prefer the Ocean to a Forest", cx, cy/4);
  image(img1,cx/3,cy/3,675,400);
  
}
void page3() {
    fill(150,50,60);
  textSize(40);
  fill(0,0,0);
  text("Cats are Definitely Better than Dogs", cx, cy/4);
    image(cat,cx/3,cy/3,675,400);
}
void page4() {
    fill(150,50,60);
  textSize(40);
  fill(0,0,0);
  text("You are Super Groovy", cx, cy/4);
    image(groovy,cx/3,cy/3,675,400);
  
}
void page5() {
    fill(150,50,60);
  textSize(40);
  fill(0,0,0);
  text("The Square Root of 642 is 27.43", cx, cy/4);
    image(math,cx/3,cy/3,675,400);
  
}
void page6() {
    fill(150,50,60);
  textSize(40);
  fill(0,0,0);
  text("You are a Russian Spy", cx, cy/4);
    image(russian,cx/3,cy/3,675,400);
  
}
void page7() {
    fill(150,50,60);
  textSize(40);
  fill(0,0,0);
  text("Newton’s Third Law is Your Favorite", cx, cy/4);
    image(newton,cx/3,cy/3,675,400);
  
}
void page8() {
    fill(150,50,60);
  textSize(40);
  fill(0,0,0);
  text("Your Biggest Fear is Slow Internet (and Zombies)", cx, cy/4);
    image(slow,cx/3,cy/3,675,400);
  
}
void page9() {
    fill(150,50,60);
  textSize(30);
  fill(0,0,0);
  text("Patricia is the Funniest Person You Know #ClassClown2020", cx, cy/4);
    image(clown,cx/3,cy/3,675,400);
  
}
void page10() {
    fill(150,50,60);
  textSize(40);
  fill(0,0,0);
  text("Yao Ming over Lebron", cx, cy/4);
    image(yao,cx/3,cy/3,675,400);
  
}
void page11() {
    fill(150,50,60);
  textSize(40);
  fill(0,0,0);
  text("Google Drive is the Best!", cx, cy/4);
    image(drive,cx/3,cy/3,675,400);
  
}

//Result Page(s)
void page12() {
   fill(150,150,160);
  textSize(45);
  fill(0,0,0);
  if (score==0) {
  text("The Magical Musk Deer", cx, cy/4);
  image(deer,cx/3,cy/3,650,400);
  }
  if (score==1) {
  text("Infectious Proteobacteria", cx, cy/4);
  image(bacteria,cx/3,cy/3,650,400);
  }
  if (score==2) {
  text("Simple (but cute) Kitten", cx, cy/4);
  image(kitten,cx/3,cy/3,650,400);
  }
  if (score==3) {
  text("Bothersome Bumble Bee", cx, cy/4);
  image(bee,cx/3,cy/3,650,400);
  }
  if (score==4) {
  text("Meddlesome Mongoose", cx, cy/4);
  image(mongoose,cx/3,cy/3,650,400);
  }
  if (score==5) {
  text("Guarded Armadillo", cx, cy/4);
  image(armadillo,cx/3,cy/3,650,400);
  }
  if (score==6) {
  text("Lonely Whale", cx, cy/4);
  image(whale,cx/3,cy/3,650,400);
  }
  if (score==7) {
  text("God", cx, cy/4);
  image(god,cx/3,cy/3,650,400);
  }
  if (score==8) {
  text("Pensive Pencil Sharpener", cx, cy/4);
  image(sharpener,cx/3,cy/3,650,400);
  }
  if (score==9) {
  text("Representative Qilin", cx, cy/4);
  image(qilin,cx/3,cy/3,650,400);
  }
  if (score==10) {
  text("Baritone Bear", cx, cy/4);
  image(bear,cx/3,cy/3,650,400);
  }
}
 
void pageSelector() {
  switch(page) {
  case 0: 
    page0();
    break;
 
  /*case 1: 
    page1();
    break;*/
 
  case 2: 
    page2();
    break;
    
    case 3: 
    page3();
    break;
    
    case 4: 
    page4();
    break;
    
    case 5: 
    page5();
    break;
    
    case 6: 
    page6();
    break;
        
    case 7: 
    page7();
    break;
    
    case 8: 
    page9();
    break;    
    
    case 9: 
    page9();
    break;
        
    case 10: 
    page10();
    break;
        
    case 11: 
    page11();
    break;
        
    case 12: 
    page12();
    break;
  }
}

Tab 2: Button Class

class Button {
  static final int W = 120, H = 80, TXTSZ = 020;
  static final color BTNC = #00A0A0, HOVC = #00FFFF, TXTC = 0;
 
  final String label;
  final short x, y, xW, yH;
 
  boolean isHovering;
 
  Button(String txt, int xx, int yy) {
    label = txt;
 
    x = (short) xx;
    y = (short) yy;
 
    xW = (short) (xx + W);
    yH = (short) (yy + H);
  }
 
  void display() {
    fill(isHovering? HOVC:BTNC);
    rect(x, y, W, H);
 
    fill(TXTC);
    text(label, x + W/2, y + H/2);
  }
 
  boolean isInside() {
    return isHovering = mouseX > x & mouseX < xW & mouseY > y & mouseY < yH;
  }
}

Processing Code With Arduino:

import processing.serial.*;
import processing.sound.*;
boolean soundisplaying = false;
String myString = null;
Serial myPort;

int NUM_OF_VALUES = 2;   /** YOU MUST CHANGE THIS ACCORDING TO YOUR PROJECT **/
int[] sensorValues;      /** this array stores values from Arduino **/

//intro sound
SoundFile spirit2;

//questions
PImage img1;
PImage cat;
PImage groovy;
PImage math;
PImage russian;
PImage newton;
PImage slow;
PImage clown;
PImage yao;
PImage drive;


//results
PImage deer;
PImage bacteria;
PImage kitten;
PImage bee;
PImage mongoose;
PImage armadillo;
PImage whale;
PImage god;
PImage sharpener;
PImage qilin;
PImage bear;

static final int MAX = 2, GAP = 50, DIM = 120, RAD = DIM >> 1;
int score;
int page, cx, cy;

 
Button next;
Button yes;
Button no;
 
void setup() {
  size(900, 700);
  setupSerial(); //serial comm
  
  //intro sound
  spirit2 = new SoundFile(this, "spirit2.wav");
  
  frameRate(50);
  smooth();
 
  rectMode(CORNER);
  textAlign(CENTER, CENTER);
 
  stroke(0);
  strokeWeight(1.5);
 
  cx = width  >> 1;
  cy = height >> 1;
 
  next = new Button("START", width - Button.W - GAP, height - Button.H - GAP);
  yes = new Button ("TRUE", width/3 - Button.W-GAP, height - Button.H - GAP);
  no = new Button ("FALSE", width - Button.W-GAP, height - Button.H - GAP);
 
 //questions
  img1 =loadImage ("ocean.jpg");
  cat =loadImage ("cat.jpg");
  groovy =loadImage("groovy.jpg");
  math =loadImage("math.jpg");
  russian =loadImage("russia.jpg");
  newton =loadImage("newton.jpg");
  slow =loadImage("slow.jpg");
  clown =loadImage ("clown.jpg");
  yao =loadImage("yao.jpg");
  drive =loadImage("drive.jpg");
  
  //results
  deer =loadImage("muskdeer.jpg");
  bacteria =loadImage("bacteria.jpg");
  kitten =loadImage("kitten.jpg");
  bee =loadImage("bee.jpg");
  mongoose =loadImage ("mongoose.jpg");
  armadillo =loadImage ("armadillo.jpg");
  whale =loadImage ("whale.jpg");
  god =loadImage ("god.jpg");
  sharpener =loadImage ("sharpener.jpg");
  qilin =loadImage ("qilin.jpg");
  bear =loadImage ("bear.jpg");
  

}
 
void draw() {
  background(120,0,150);
  
  updateSerial();  //Serial Comm
  printArray(sensorValues); //these are the values
  print(sensorValues[0]);
  print(sensorValues[1]); 

  
  textSize(Button.TXTSZ);

 
  if (page==0) next.display();
  if (page>0 && page<12) yes.display(); 
  if (page>0 && page<12) no.display();
 
  pageSelector();        
}

void mousePressed() {
  if (page < MAX && next.isHovering)  ++page;
 if (yes.isHovering) {
    ++page;
    ++score;
  }
 if (no.isHovering) {
    ++page; 
  }
  redraw();
}
 
void mouseMoved() {
 
 next.isInside();
 yes.isInside();
 no.isInside();
 
 redraw();
}

 
void page0() { //title page
  fill(150,50,60);
  textSize(0100);
  fill(0,0,0);
  text("What's Your Spirit Animal?", cx, cy);
  if (soundisplaying ==false) {
      spirit2.play();
      soundisplaying = true; }
}
 /*void page1() {
    fill(150,50,60);
  textSize(0100);
  fill(0,0,0);
  text("Question 1?", cx, cy);
  
}*/
 void page2() {
    fill(0,0,160);
  textSize(40);
  fill(0,0,0);
  text("      You Prefer the Ocean to a Forest", cx, cy/4);
  image(img1,cx/3,cy/3,675,400);
  
}
void page3() {
    fill(150,50,60);
  textSize(40);
  fill(0,0,0);
  text("Cats are Definitely Better than Dogs", cx, cy/4);
    image(cat,cx/3,cy/3,675,400);
}
void page4() {
    fill(150,50,60);
  textSize(40);
  fill(0,0,0);
  text("You are Super Groovy", cx, cy/4);
    image(groovy,cx/3,cy/3,675,400);
  
}
void page5() {
    fill(150,50,60);
  textSize(40);
  fill(0,0,0);
  text("The Square Root of 642 is 27.43", cx, cy/4);
    image(math,cx/3,cy/3,675,400);
  
}
void page6() {
    fill(150,50,60);
  textSize(40);
  fill(0,0,0);
  text("You are a Russian Spy", cx, cy/4);
    image(russian,cx/3,cy/3,675,400);
  
}
void page7() {
    fill(150,50,60);
  textSize(40);
  fill(0,0,0);
  text("Newton’s Third Law is Your Favorite", cx, cy/4);
    image(newton,cx/3,cy/3,675,400);
  
}
void page8() {
    fill(150,50,60);
  textSize(40);
  fill(0,0,0);
  text("Your Biggest Fear is Slow Internet (and Zombies)", cx, cy/4);
    image(slow,cx/3,cy/3,675,400);
  
}
void page9() {
    fill(150,50,60);
  textSize(30);
  fill(0,0,0);
  text("Patricia is the Funniest Person You Know #ClassClown2020", cx, cy/4);
    image(clown,cx/3,cy/3,675,400);
  
}
void page10() {
    fill(150,50,60);
  textSize(40);
  fill(0,0,0);
  text("Yao Ming over Lebron", cx, cy/4);
    image(yao,cx/3,cy/3,675,400);
  
}
void page11() {
    fill(150,50,60);
  textSize(40);
  fill(0,0,0);
  text("Google Drive is the Best!", cx, cy/4);
    image(drive,cx/3,cy/3,675,400);
  
}

//Result Page(s)
void page12() {
   fill(150,150,160);
  textSize(45);
  fill(0,0,0);
  if (score==0) {
  text("The Magical Musk Deer", cx, cy/4);
  image(deer,cx/3,cy/3,650,400);
  }
  if (score==1) {
  text("Infectious Proteobacteria", cx, cy/4);
  image(bacteria,cx/3,cy/3,650,400);
  }
  if (score==2) {
  text("Simple (but cute) Kitten", cx, cy/4);
  image(kitten,cx/3,cy/3,650,400);
  }
  if (score==3) {
  text("Bothersome Bumble Bee", cx, cy/4);
  image(bee,cx/3,cy/3,650,400);
  }
  if (score==4) {
  text("Meddlesome Mongoose", cx, cy/4);
  image(mongoose,cx/3,cy/3,650,400);
  }
  if (score==5) {
  text("Guarded Armadillo", cx, cy/4);
  image(armadillo,cx/3,cy/3,650,400);
  }
  if (score==6) {
  text("Lonely Whale", cx, cy/4);
  image(whale,cx/3,cy/3,650,400);
  }
  if (score==7) {
  text("God", cx, cy/4);
  image(god,cx/3,cy/3,650,400);
  }
  if (score==8) {
  text("Pensive Pencil Sharpener", cx, cy/4);
  image(sharpener,cx/3,cy/3,650,400);
  }
  if (score==9) {
  text("Representative Qilin", cx, cy/4);
  image(qilin,cx/3,cy/3,650,400);
  }
  if (score==10) {
  text("Baritone Bear", cx, cy/4);
  image(bear,cx/3,cy/3,650,400);
  }
}
 
void pageSelector() {
  switch(page) {
  case 0: 
    page0();
    break;
 
  /*case 1: 
    page1();
    break;*/
 
  case 2: 
    page2();
    break;
    
    case 3: 
    page3();
    break;
    
    case 4: 
    page4();
    break;
    
    case 5: 
    page5();
    break;
    
    case 6: 
    page6();
    break;
        
    case 7: 
    page7();
    break;
    
    case 8: 
    page9();
    break;    
    
    case 9: 
    page9();
    break;
        
    case 10: 
    page10();
    break;
        
    case 11: 
    page11();
    break;
        
    case 12: 
    page12();
    break;
  }
}


void setupSerial() {
  printArray(Serial.list());
  myPort = new Serial(this, Serial.list()[ 0 ], 9600);


  myPort.clear();
  myString = myPort.readStringUntil( 10 ); 
  myString = null;

  sensorValues = new int[NUM_OF_VALUES];
}

void updateSerial() {
  while (myPort.available() > 0) {
    myString = myPort.readStringUntil( 10 ); 
    if (myString != null) {
      String[] serialInArray = split(trim(myString), ",");
      if (serialInArray.length == NUM_OF_VALUES) {
        for (int i=0; i<serialInArray.length; i++) {
          sensorValues[i] = int(serialInArray[i]);
        }
      }
      println(sensorValues[0]+','+ sensorValues[1]);
     }
  }
}

Tab 2 Button Class;

class Button {
  static final int W = 120, H = 80, TXTSZ = 020;
  static final color BTNC = #00A0A0, HOVC = #00FFFF, TXTC = 0;
 
  final String label;
  final short x, y, xW, yH;
 
  boolean isHovering;
 
  Button(String txt, int xx, int yy) {
    label = txt;
 
    x = (short) xx;
    y = (short) yy;
 
    xW = (short) (xx + W);
    yH = (short) (yy + H);
  }
 
  void display() {
    fill(isHovering? HOVC:BTNC);
    rect(x, y, W, H);
 
    fill(TXTC);
    text(label, x + W/2, y + H/2);
  }
 
  boolean isInside() {
    return isHovering = mouseX > x & mouseX < xW & mouseY > y & mouseY < yH;
  }
}


Arduino Code:

void setup() {
  
  Serial.begin(9600);
}

// 
void loop() {
 
  int sensorValue = analogRead(A0);
  int sensorValue2 = analogRead(A1);
  
  if(sensorValue>100){
 
  Serial.print("1");}
  else{ Serial.print("0");}
  
  Serial.print(",");
  if(sensorValue2>100){
 
  Serial.println("1");}
  else{ Serial.println("0");}
  
  delay(100); 

Interaction Lab: Final Project- Dancing Origami (Dan)

Here come the final! As planned in the final project essay and idea presentation, we built our wired dancing origami project. We got this idea from a Japanese team, they used three months to build this project, and heres the link to the inspiration video.

Weve wanted to do this really interesting project since midterm, but because the time was limited, we only got all those materials we needed. For finalwe wanted to finish what we started and failed in midterm, we started by giving tests to two electromagnets and one small magnet. Previously, we used relays to control big currents when turning on and off small currents, but there was a significant question that relays can easily be overheated. After midterm we learned about H-bridges and Dan said we can use it to achieve our origami idea, so we borrowed H-bridges to change the direction of big currents so that we could change the polarity of each electromagnets and then make the feet of the bird move better.  It was not easy to figure out each pole on the H-bridge, and we got the map of the H-bridge we used online with Dans help, and Tony finished the first H-bridges connection while I was folding the origami and preparing for the laser cutter. The test was successful, the tiny magnet flipped around on Tonys hand (as in the video below).

WechatIMG7482 WechatIMG7479

Then, we could use the laser cutter to build the box and the interface. I used online box generator to create the box file. And then changed the line into 0.1pt stroke width. We chose wooden board as four sides and transparent acrylic board as the top, because we need the interface to be rather smooth. With Marcelas help, we got the box!

Box

After these, Tony and me discussed about different moves of the origami, then he coded in Arduino, following those moves we designed together. Thing went pretty smooth until we found that the small magnet was extremely unstable. It had several problems: 1. It jumped and moved around randomly when the polarity of two electromagnets switched. We must find a way to make it stay on the interface and finish those moves we designed. 2. When we put two magnets on the interface, they either repelled or attracted. So we needed to figure out the distance between them.

Special thanks to Dan haha, who gave us two not that powerful magnets, which solved big part of our trouble.

We first solved the problem on the paper bird. We used a piece of very thin paper to fold it, so its legs were too weak to support the whole body. Tony thought that we can add sticks or wires in its legs, but those would be too strong and so that the distance between two feet would be uneasy to change. Then I came up with the idea that we can use conductive tape to support it, and the idea finally worked!

WechatIMG7481 WechatIMG7478

During experimenting, we found out most of the times, the magnet went out crazily to other places on the acrylic board. We thought of several ways to control it, for example, using foam board, wires, glue, tape… But these are too difficult to restrain the magnet, more importantly, I thought that it would affect the whole result of the project, because these things didnt look good.  :)

Then I came up with the idea that we could use those magnets we bought online, those strong ones, as shield, which is to say, we could place them around electromagnets, and make the polarity the same as the bottom of two magnets on the acrylic board, so that the magnet field they created could become a shield, preventing two legs from going out.

Tony also thought that the idea was practical, so we immediately started placing those magnets around our project.

WechatIMG7477

But obviously, different magnet fields that those electromagnets, small magnets and wires produced are not stable at all, so we need to adjust them again and again. Along with the distance between electromagnets, we tested for a long time, and eventually found a rather balanced positions for all those elements.

Then, I coded Processing. Using KeyPressed function, we assign different moves with numbers on the keyboard to control the birds moves. Heres the demo we made for the presentation:

Conclusion: The project was really interesting, but there were many unstable factors, such as different magnet fields, and those electromagnets could get heated and melted the glue, therefore move around and get messy. However, we did get it worked at some point.

Things learned / to share:  1.Start early. 2.Think of as many ways as you can to solve your problems and try them. It’s not cool to give up your original idea or make compromises. 3. Don’t complain about repeating your work, especially in our case. 🙂

//Processing
//Processing
import processing.serial.*; 
Serial myPort; 
int val; 
void setup() { 
  printArray(Serial.list());
  // this prints out the list of all 
     // available serial ports on your computer.

  myPort = new Serial(this, Serial.list()[3], 9600); 
// Change the Serial.list()[0] to your port
  } 
void draw() { 
  // to send a value to the Arduino 
  //if (keyPressed) { 
  //  myPort.write(key); 
  //  print(key);1
  //  }
  if (keyPressed){
  myPort.write(val);
  } 
}
  void keyPressed(){
  if(key == '1'){
  val = 1;
  }
  if(key == '2'){
  val = 2;
  }
  if(key == '3'){
  val = 3;
  }
  if(key == '4'){
  val = 4;
  }
  if(key == '5'){
  val = 5;
  }
  if(key == '6'){
  val = 6;
  }
  if(key == '7'){
  val = 7;
  }
  
  }
  
  
//Arduino
//define a starting&ending state
void move0 () {
  digitalWrite(pull1,HIGH);
  digitalWrite(push1,LOW);
  digitalWrite(pull2,LOW);
  digitalWrite(push2,LOW);
  digitalWrite(pull3,LOW);
  digitalWrite(push3,LOW);
  digitalWrite(pull4,HIGH);
  digitalWrite(push4,LOW);
  digitalWrite(pull5,LOW);
  digitalWrite(push5,LOW);
  digitalWrite(pull6,LOW);
  digitalWrite(push6,LOW);
}


void move1 (int pace) {
  move0();
  digitalWrite(push6,HIGH);
  digitalWrite(pull4,LOW);
  digitalWrite(push4,HIGH);
  digitalWrite(pull5,HIGH);
  digitalWrite(push5,LOW);
  delay(pace);
  digitalWrite(pull4,HIGH);
  digitalWrite(push4,LOW);
  digitalWrite(pull5,LOW);
  digitalWrite(push5,HIGH);
  delay(pace);
  digitalWrite(pull4,LOW);
  digitalWrite(push4,HIGH);
  digitalWrite(pull5,HIGH);
  digitalWrite(push5,LOW);
  delay(pace);
  digitalWrite(pull4,HIGH);
  digitalWrite(push4,LOW);
  digitalWrite(pull5,LOW);
  digitalWrite(push5,HIGH);
  move0();
  delay(pace);
}


void move2 (int pace) {
  move0();
  digitalWrite(push3,HIGH);
  digitalWrite(pull1,LOW);
  digitalWrite(push1,HIGH);
  digitalWrite(pull2,HIGH);
  digitalWrite(push2,LOW);
  delay(pace);
  digitalWrite(pull1,HIGH);
  digitalWrite(push1,LOW);
  digitalWrite(pull2,LOW);
  digitalWrite(push2,HIGH);
  delay(pace);
  digitalWrite(pull1,LOW);
  digitalWrite(push1,HIGH);
  digitalWrite(pull2,HIGH);
  digitalWrite(push2,LOW);
  delay(pace);
  digitalWrite(pull1,HIGH);
  digitalWrite(push1,LOW);
  digitalWrite(pull2,LOW);
  digitalWrite(push2,HIGH);
  move0();
  delay(pace);
  
}


void move3(int pace){
  move0();
  digitalWrite(push5,HIGH);
  digitalWrite(pull4,LOW);
  digitalWrite(push4,HIGH);
  digitalWrite(pull6,HIGH);
  digitalWrite(push6,LOW);
  delay(pace);
  digitalWrite(pull4,HIGH);
  digitalWrite(push4,LOW);
  digitalWrite(pull6,LOW);
  digitalWrite(push6,HIGH);
  delay(pace);
  digitalWrite(pull4,LOW);
  digitalWrite(push4,HIGH);
  digitalWrite(pull6,HIGH);
  digitalWrite(push6,LOW);
  delay(pace);
  digitalWrite(pull4,HIGH);
  digitalWrite(push4,LOW);
  digitalWrite(pull6,LOW);
  digitalWrite(push6,HIGH);
  move0();
  delay(pace);
}


void move4 (int pace) {
   move0();
  digitalWrite(push2,HIGH);
  digitalWrite(pull1,LOW);
  digitalWrite(push1,HIGH);
  digitalWrite(pull3,HIGH);
  digitalWrite(push3,LOW);
  delay(pace);
  digitalWrite(pull1,HIGH);
  digitalWrite(push1,LOW);
  digitalWrite(pull3,LOW);
  digitalWrite(push3,HIGH);
  delay(pace);
  digitalWrite(pull1,LOW);
  digitalWrite(push1,HIGH);
  digitalWrite(pull3,HIGH);
  digitalWrite(push3,LOW);
  delay(pace);
  digitalWrite(pull1,HIGH);
  digitalWrite(push1,LOW);
  digitalWrite(pull3,LOW);
  digitalWrite(push3,HIGH);
  move0();
  delay(pace);
}


void move5(int pace) {
  move0;
  digitalWrite(push6,HIGH);
  digitalWrite(pull4,LOW);
  digitalWrite(push4,HIGH);
  digitalWrite(pull5,HIGH);
  digitalWrite(push5,LOW);
  delay(pace);
  digitalWrite(pull4,HIGH);
  digitalWrite(push4,LOW);
  digitalWrite(pull5,LOW);
  digitalWrite(push5,HIGH);
  //move0();
  delay(pace);
  digitalWrite(push3,HIGH);
  digitalWrite(pull1,LOW);
  digitalWrite(push1,HIGH);
  digitalWrite(pull2,HIGH);
  digitalWrite(push2,LOW);
  delay(pace);
  digitalWrite(pull1,HIGH);
  digitalWrite(push1,LOW);
  digitalWrite(pull2,LOW);
  digitalWrite(push2,HIGH);
  delay(pace);
  //move0();
  digitalWrite(push6,HIGH);
  digitalWrite(pull4,LOW);
  digitalWrite(push4,HIGH);
  digitalWrite(pull5,HIGH);
  digitalWrite(push5,LOW);
  delay(pace);
  digitalWrite(pull4,HIGH);
  digitalWrite(push4,LOW);
  digitalWrite(pull5,LOW);
  digitalWrite(push5,HIGH);
  //move0();
  delay(pace);
  digitalWrite(push3,HIGH);
  digitalWrite(pull1,LOW);
  digitalWrite(push1,HIGH);
  digitalWrite(pull2,HIGH);
  digitalWrite(push2,LOW);
  delay(pace);
  digitalWrite(pull1,HIGH);
  digitalWrite(push1,LOW);
  digitalWrite(pull2,LOW);
  digitalWrite(push2,HIGH);
  move0();
  delay(pace);
}


void move6(int pace) {
  move0();
  digitalWrite(push5,HIGH);
  digitalWrite(pull4,LOW);
  digitalWrite(push4,HIGH);
  digitalWrite(pull6,HIGH);
  digitalWrite(push6,LOW);
  delay(pace);
  digitalWrite(pull4,HIGH);
  digitalWrite(push4,LOW);
  digitalWrite(pull6,LOW);
  digitalWrite(push6,HIGH);
  delay(pace);
  digitalWrite(push2,HIGH);
  digitalWrite(pull1,LOW);
  digitalWrite(push1,HIGH);
  digitalWrite(pull3,HIGH);
  digitalWrite(push3,LOW);
  delay(pace);
  digitalWrite(pull1,HIGH);
  digitalWrite(push1,LOW);
  digitalWrite(pull3,LOW);
  digitalWrite(push3,HIGH);
  delay(pace);
  digitalWrite(push5,HIGH);
  digitalWrite(pull4,LOW);
  digitalWrite(push4,HIGH);
  digitalWrite(pull6,HIGH);
  digitalWrite(push6,LOW);
  delay(pace);
  digitalWrite(pull4,HIGH);
  digitalWrite(push4,LOW);
  digitalWrite(pull6,LOW);
  digitalWrite(push6,HIGH);
  delay(pace);
  digitalWrite(push2,HIGH);
  digitalWrite(pull1,LOW);
  digitalWrite(push1,HIGH);
  digitalWrite(pull3,HIGH);
  digitalWrite(push3,LOW);
  delay(pace);
  digitalWrite(pull1,HIGH);
  digitalWrite(push1,LOW);
  digitalWrite(pull3,LOW);
  digitalWrite(push3,HIGH);
  move0();
  delay(pace);
}


void move7 (int pace) {
  move1(pace);
  move1(pace);
  move2(pace);
  move2(pace);
  move3(pace);
  move3(pace);
  move4(pace);
  move4(pace);
  move5(pace);
  move5(pace);
  move6(pace);
  move6(pace);
}