“Feeling” real in VR

While using the HTC Vive, I enjoyed it most when I pulled back a bow and felt the vibration and sound telling me that I had put in effort to pull back the bow. After shooting many arrows, my arm had actually started to feel tired (but not nearly as tired as I would be from shooting that many arrows in real life). To enhance it more, I would want to be able to lean over the parapet just enough to see down- the limited area of motion became annoying over time. However, for my first time playing, it was probably a good limitation that kept me from feeling sick.

The part I liked least was trying to figure out when to point and click, and when I should grab with the trigger. It wasn’t clear when each needed to be done. This will likely be solved as more VR games are created. They will need to come up with a common design language that will become as natural as moving a mouse on a computer.

Bouncing sled

Description

The servo motor is placed on top of a cardboard sled that has two legs in front to keep the front up. The servo motor base it tied with twine and taped to the cardboard. The servo motor head is taped to legos. The legos have a rubber band through them that makes the hinge try to extend like a prosthetic leg. When it pushes down, the movement pushes the sled forward. On the return trip, the knee flexes as it is pulled the other direction. The Arduino is also on the sled.

Materials

  • 1 Arduino
  • 1 servo motor
  • wires
  • masking tape
  • cardboard
  • twine

Image

Bouncy Sled

Code

int servoPin = 7;      // Control pin for servo motor

int pulseWidth = 0;    // Amount to pulse the servo
long lastPulse = 0;    // the time in millisecs of the last pulse
int refreshTime = 20;  // the time in millisecs needed in between pulses
int minPulse = 500;   // minimum pulse width
long lastUpdate = 0;  // last position update time in milliseconds
int refreshUpdateTime = 200;  // time in milliseconds between position updates

void setup() {
  pinMode(servoPin, OUTPUT);  // Set servo pin as an output pin
  pulseWidth = minPulse;      // Set the motor position to the minimum
  Serial.begin(9600);         // connect to the serial port
  Serial.println("servo_serial_better ready");
}

void loop() {
  if (millis() - lastUpdate >= refreshUpdateTime) {
    if (pulseWidth < 700) {
      pulseWidth = 720;
    } else {
      pulseWidth = 500;
    }
    
    Serial.print("moving servo to ");
    Serial.println(pulseWidth,DEC);
    lastUpdate = millis();
  }
  updateServo();   // update servo position
}

// called every loop(). 
void updateServo() {
  // pulse the servo again if the refresh time (20 ms) has passed:
  if (millis() - lastPulse >= refreshTime) {
    digitalWrite(servoPin, HIGH);   // Turn the motor on
    delayMicroseconds(pulseWidth);  // Length of the pulse sets the motor position
    digitalWrite(servoPin, LOW);    // Turn the motor off
    lastPulse = millis();           // save the time of the last pulse
  }
}

Angry Turtle

Description

This Lego turtle gets angry and vibrates whenever there is light on the light sensor. There is a DC motor inside the turtle. The motor has a piece of cork on it, so running the motor makes it vibrate. If the light sensor reading is over 500 (about 50%), then the turtle will pulse vibration for 0.2 seconds, then pause for 0.5 seconds. This makes it sound angry without being overwhelming. The speed of the motor is directly related to the light reading.

Materials

  • 1 cork
  • Legos
  • Masking tape
  • 1 Arduino
  • 1 DC motor
  • 1 red LED
  • 1 light sensor
  • 1 220 Ω resistor
  • 1 10 KΩ resistor
  • 1 1 KΩ resistor
  • wires
  • 1 diode
  • 1 battery pack
  • 2 AA batteries
  • 1 transistor

Image

Angry Turtle

int lightSensorPin = 0;   // select the input pin for the light sensor
int motorPin = 9; // select the pin for the Motor
int val = 0;      // variable to store the value coming from the sensor
void setup() {
  Serial.begin(9600);
}
void loop() {
  val = analogRead(lightSensorPin);    // read the value from the sensor, between 0 - 1024
  Serial.println(val);
  if (val > 500) {
      analogWrite(motorPin, val/4); // analogWrite can be between 0-255
    delay(200);
  }
  analogWrite(motorPin, 0); // turn off
  delay(500);

Trash drawer

The thoughtless act I have been watching is chest of drawers that is regularly used as a trash can. Because the drawers have been placed on a sidewalk, they appear to be trash, but they have not moved for at least a few days (if not longer). One drawer was placed on top and is shaped correctly to hold things, and has been used to hold trash. On the surface, this appears to have a very simple solution: put a trash can there. However, walking by the drawers for a few days in a row, I have seen the trash change. Someone has taken the time to empty the trash on top. It is still unclear why the entire set has not been taken to a dump.

Trash Drawer

Goodnight Bowl

Description

This bowl acts as a night-light and glows red when picked up while it is dark. Upon being set down, it plays “Twinkle, Twinkle Little Star” and the light slowly fades. During the day when there is light, it appears to be just a bowl of cotton balls. In this way, it acts as an object that will put the user to sleep at the end of their day.

I used a force-sensitive resistor on the bottom of the bowl to measure when it is set down, and I used a light sensor on the bowl to measure whether it is dark around the bowl. A piezo speaker plays music.

Materials

  • 1 ceramic bowl
  • 1 force-sensitive resistor
  • masking tape
  • 1 Arduino
  • cotton balls
  • 1 red LED
  • 1 light sensor
  • 1 piezo speaker
  • 1 22 KΩ resistor
  • 3 10 KΩ resistors
  • wires

Images

Goodnight Bowl

Goodnight Bowl

Code

int ledPin = 9;     // select the output pin for LED
int speakerPin = 7; // select the output pin for speaker
int lightSensorPin = A0;   // select input for light sensor
int forceSensorPin = A2;   // select input for force sensor

int forceValue = 0;  // variable to store force value
int lightValue = 0;  // variable to store light value
int wasHeld = 0; // boolean for if force was very low in last loop

byte names[] = {'c', 'd', 'e', 'f', 'g', 'a', 'b', 'C'};  
int tones[] = {1915, 1700, 1519, 1351, 1275, 1136, 1014, 956}; // f was changed to f sharp (370 Hz)
byte melody[] = "7d1p7d1p7a1p7a1p7b1p7b1p8a6a2p7g1p7g1p7f1p7f1p7e1p7e1p8d6d2p";
int count = 0;
int count2 = 0;
int count3 = 0;
int MAX_COUNT = 24;
int LIGHT_THRESHOLD = 200;
int FORCE_THRESHOLD = 100;

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

void loop() {
  int max_subloops;
  // put your main code here, to run repeatedly:
  lightValue = analogRead(lightSensorPin);
  // turn the ledPin on, convert from 0-1023 scale to 0-255
  //analogWrite(ledPin, (lightValue - 300) / 4); //forceValue / 4 + 

  forceValue = analogRead(forceSensorPin);
  if (forceValue >= FORCE_THRESHOLD && wasHeld && lightValue < LIGHT_THRESHOLD) {
    wasHeld = 0;
    digitalWrite(speakerPin, LOW);
    max_subloops = sizeof(melody) / 2;
    for (count = 0; count < max_subloops; count++) {
      analogWrite(ledPin, 255 * (max_subloops - count - 1) / max_subloops);
      // 48 converts from byte to int
      // 30 is length of time
      for (count3 = 0; count3 <= (melody[count*2] - 48) * 30; count3++) {  
        for (count2=0;count2<8;count2++) {        // 8 is the size of names
          if (names[count2] == melody[count*2 + 1]) {       
            digitalWrite(speakerPin,HIGH);
            delayMicroseconds(tones[count2]);
            digitalWrite(speakerPin, LOW);
            delayMicroseconds(tones[count2]);
          } 
          if (melody[count*2 + 1] == 'p') {
            // make a pause of a certain size
            digitalWrite(speakerPin, 0);
            delayMicroseconds(500);
          }
        }
      }
    }
   } else if (forceValue < FORCE_THRESHOLD) {
    if (lightValue < LIGHT_THRESHOLD) {
     digitalWrite(ledPin, HIGH); 
    }
    wasHeld = 1;
  } else {
    wasHeld = 0;
  }

  Serial.print(lightValue);
  Serial.println();
}

MagSafe power adapter

The Apple MagSafe power adapter achieved the basic goal of a power adapter of connecting my computer to power, but once I saw how it would easily pull out when someone tripped over it, I wondered why it wasn’t a standard feature of power cords for computers to come out easily. It is the kind of feature that is only created when a power cord is tested in the many places people use them rather than in nice controlled conditions. The portability of a laptop created new challenges that were never an issue with desktop computers. The MagSafe made a MacBook portable even while plugged in.

Lab 4: FSR game input

Description

I used the force sensitive resistor as an input for a game in Processing where the player flies an airplane to dodge walls. The force on the FSR determines the height of the airplane, which travels past obstacles at a constant speed. Originally, I wanted to have a trail following the airplane to show where it had been, but to improve the feel of the game, it was reduced to very small flames behind airplane to give a sense of the movement. An LED on the Arduino correlates its brightness with the FSR force for debugging when the serial connection has trouble.

I built a force diffuser from paper towels sandwiched between two stacks of sticky notes. The sticky note stacks were approximately the right size to be pushed with a hand, and the paper towels worked well because the amount of force diffused could be easy modulated by adding or removing paper towels. The sticky note stacks could be replaced with any hard object of the same size. While diffusing force is very useful for measuring forces outside of the FSR’s range, I found that when using the FSR as an input device for a game, it was best to have as little diffusing as possible to allow a single finger to have the most control.

Materials

  • Arduino
  • 1 Force Sensitive Resistor (FSR)
  • 1 LED
  • 1 22 KΩ resistor
  • 1 10 KΩ resistor
  • 10 wires

Images

Sticky Note force diffuser
Screenshot from FSR game

Arduino Code

int forceSensorPin = A0;    // select the input pin for the rate potentiometer
int ledPin = 9;      // select the pin for the LED
int forceValue = 0;  // variable to store rate pot value
void setup() {
  // declare the ledPin as an OUTPUT:
  pinMode(ledPin, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  // read the value from the sensors:
  forceValue = analogRead(forceSensorPin);
  
  // turn the ledPin on, convert from 0-1023 scale to 0-255
  analogWrite(ledPin, forceValue / 4);
  // stop the program for 10 milliseconds:
  delay(10);
  // turn the ledPin off:
  digitalWrite(ledPin, LOW);
  Serial.print(forceValue);
  Serial.println();
}

Processing Code

/* PROCESSING FORCE GAME
 * ----------------------
 *
 * Using a force input, guide an airplane
 * to avoid obstacles.
 *
 * Created 23 September 2016
 * Sam Meyer
 */
import processing.serial.*;
// Change this to the portname your Arduino board
String portname = "/dev/cu.usbmodem1411"; // or "COM5"
Serial port;
String buf="";
int cr = 13; // ASCII return == 13
int lf = 10; // ASCII linefeed == 10

int serialVal = 0;
int xPos = 1000;
int rectHeight = 50;
float rand;
boolean gameOver = false;

EvilRectangle er;
ArrayList<EvilRectangle> erList;
int rCounter = 0;
long iDraw = 0;
int rectRate = 60;
PlayerIcon airplane;

class PlayerIcon {
  float x;
  float y;
  float y2; // green flame
  float y3; // blue flame
  
  PlayerIcon() {
    x = 150;
    y = 0;
    y2 = 0;
    y3 = 0;
  }
  
  void draw() {
    //blue flame
    fill(0, 0, 255);
    //ellipse(x3, y3, 100, 15);
    triangle(x - 50, y3 + 8, x - 50, y3 - 8, x - 70, y3); 
    triangle(x - 50, y3 + 8, x - 50, y3 - 8, x - 48, y3); 
    // green flame
    fill(0, 128, 0);
    //ellipse(x2, y2, 100, 23);
    triangle(x - 50, y2 + 12, x - 50, y2 - 12, x - 60, y2); 
    triangle(x - 50, y2 + 12, x - 50, y2 - 12, x - 45, y2); 
    // airplane
    fill(255, 255, 255);
    ellipse(x, y, 100, 25);
    triangle(x - 10, y + 50, x - 10, y - 50, x + 30, y);
    triangle(x - 50, y + 20, x - 50, y - 20, x - 20, y);
  }
  
  void setY(float val) {
    // move y values back through the following shapes
    y3 = y2;
    y2 = y;
    y = val;
  }
}

class EvilRectangle {
  float x;
  float y;
  float w;
  float h;
  EvilRectangle(float a, float b, float c, float d) {
    x = a;
    y = b;
    w = c;
    h = d;
  }

  void draw() {
    fill(255, 0, 0);
    rect(x, y, w, h);
  }
  
  boolean checkCollision(float xC, float yC) {
    return (xC > x) && (xC < x + w) && (yC > y) && (yC < y + h);
  }
}

void setup() {
 size(1000,600);
 frameRate(30);
 smooth();
 background(40,40,40);
 noStroke();
 port = new Serial(this, portname, 9600);
 er = new EvilRectangle(xPos, 600-rectHeight, 55, rectHeight);
 erList = new ArrayList<EvilRectangle>();
 erList.add(er);
 airplane = new PlayerIcon();
}

void draw() {

 if (gameOver) {
   fill(200);
   textSize(52);
   text("Crashed!\nDistance: " + str(int(iDraw)), 100, 200);
 } else {
   // increment counters
   iDraw += 1;
   rCounter += 1;
   
   // erase the screen
   background(40, 40, 40);
     
   // draw the airplane
   airplane.setY(max(50, 600 - serialVal/2 - 50));
   airplane.draw();
   
   // check for collsions
   for (EvilRectangle er : erList) {
     if (er.checkCollision(airplane.x + 50, airplane.y) || 
         er.checkCollision(airplane.x - 50, airplane.y) ||
         er.checkCollision(airplane.x, airplane.y + 50) ||
         er.checkCollision(airplane.x, airplane.y - 50)) {
       gameOver = true;
     }
   }
  
   // draw the rectangle
   for (EvilRectangle er : erList) {
     er.draw();
     er.x -= 5;
   }
   
   // draw current level
   textSize(16);
   fill(200);
   text("Current\nlevel: " + str(61 - rectRate), 10, 40);
   
   // add new rectangles
   if (rCounter >= rectRate) {
     rCounter = 0;
     if (iDraw < 240) {
       rectHeight = 50 * int(iDraw) / 30;
     } else {
       rectHeight = int(random(50, 400));
     }
     
     // increase speed
     if (iDraw > 400 & rectRate > 25) {
       rectRate -= 1;
     }
     
     // pick rectangle direction
     rand = random(1);
     if (rand < 0.4) {
       // bottom rect
       erList.add(new EvilRectangle(xPos, 600-rectHeight, 55, rectHeight));
     } else if (rand > 0.7) {
       // top and bottom
       erList.add(new EvilRectangle(xPos, 600-rectHeight/2, 55, rectHeight/2));
       erList.add(new EvilRectangle(xPos, 0, 55, rectHeight/2));
     } else {
       // top rect
       erList.add(new EvilRectangle(xPos, 0, 55, rectHeight));
     }
     
     // clean up rectangles that have left the screen
     for (int i = erList.size() - 1; i >= 0; i--) {
       if (erList.get(i).x < -100) {
         erList.remove(i);
       }
     }
     
     // reset rCounter
     rCounter = 0;
   }
 }
}

// called whenever serial data arrives
void serialEvent(Serial p) {
 int c = port.read();
 if (c != lf && c != cr) {
 buf += char(c);
 }
 if (c == lf) {
 serialVal = int(buf);
 // println("val="+serialVal);
 buf = "";
 }
}

Variable rate LED

Description


This circuit uses two potentiometers, one to control the brightness of the LED and the other to control the blinking rate. Within each blink, the brightness is held constant. All measurements are done using analog inputs on the Arduino.

Components


  • Arduino
  • 1 LED
  • 1 Resistor (220Ω)
  • 7 Wires
  • 1 Breadboard
  • 2 Potentiometers

Code



int rateSensorPin = A0;    // select the input pin for the rate potentiometer
int brightSensorPin = A2;  // select the input pin for the brightness potentiometer
int ledPin = 9;      // select the pin for the LED
int rateValue = 0;  // variable to store rate pot value
int brightValue = 0;  // variable to store brightness pot value
void setup() {
  // declare the ledPin as an OUTPUT:
  pinMode(ledPin, OUTPUT);
}

void loop() {
  // read the value from the sensors:
  rateValue = analogRead(rateSensorPin);
  brightValue = analogRead(brightSensorPin);
  
  // turn the ledPin on, convert from 0-1023 scale to 0-255
  analogWrite(ledPin, brightValue/4);
  // stop the program for <rateVale> milliseconds:
  delay(rateValue);
  // turn the ledPin off:
  digitalWrite(ledPin, LOW);
  // stop the program for for <rateValue> milliseconds:
  delay(rateValue);
}
Potentiometer circuit

TUI Taxonomy

I wanted to see where Hiroshi Ishii’s pin-based shape display used for telepresence and other projects would fall on Fishkin’s taxonomy. Is it a full metaphor, where it is just an object to be interacted with, or is it only a verb metaphor when moving hands in one location moves them in another location? Is that distant? The effects are both local and far away. It seems to cross boundaries and be hard to define in the taxonomy.

For most other tangible user interfaces, Fishkin’s taxonomy was a great way to consider a spectrum of different interfaces that are called TUI’s. I appreciated the discussion about the proper amount of metaphor, and it helped me to consider how metaphor is helpful for learning, but strong metaphor can limit how an interface will be used.

With an interest in calm computing, I would like to see a similar taxonomy made that considers how much information is obtained without conscious effort. Many TUI’s seek to let the hands feel and guide an interface without the user explicitly thinking about how they feel, which is similar to the background attention given to calm computing.

Multi-color LED control

Description

I used an Arduino to control three LED’s: red, green, and blue. With pulse width modification, I control the brightness of each. They are all under a plastic container that has been covered with a couple layers of bubble wrap to act as a diffuser. The brightness of each can be controlled by using serial communication sending combinations of the letters “r”, “g”, and “b”. For example, “rrrrrbbbb” will result in a purple light.

Components

  • Arduino
  • 3 LED’s: red, green, blue
  • 3 Resistors (220Ω)
  • 5 Wires
  • 1 Breadboard
  • 1 Plastic storage container
  • Bubble wrap

Code

 



char serInString[100];  // array that will hold the different bytes of the string. 100=100characters;
                        // -> you must state how long the array will be else it won't work properly
char colorCode;
int colorVal;
int redVal;
int greenVal;
int blueVal;
int redPin   = 9;   // Red LED,   connected to digital pin 9
int greenPin = 10;  // Green LED, connected to digital pin 10
int bluePin  = 11;  // Blue LED,  connected to digital pin 11
void setup() {
  pinMode(redPin,   OUTPUT);   // sets the pins as output
  pinMode(greenPin, OUTPUT);   
  pinMode(bluePin,  OUTPUT);
  Serial.begin(9600);
  analogWrite(redPin,   127);   // set them all to mid brightness
  analogWrite(greenPin, 127);   // set them all to mid brightness
  analogWrite(bluePin,  127);   // set them all to mid brightness
  Serial.println("enter color command using r's, b's, and g's (e.g. 'rrrrrrggggg') :");  
}
void loop () {
  // clear the string
  memset(serInString, 0, 100);
  //read the serial port and create a string out of what you read
  readSerialString(serInString);
  // initialize all back to zero for each loop
  redVal = 0;
  greenVal = 0;
  blueVal = 0;
  colorCode = serInString[0];
  if( colorCode == 'r' || colorCode == 'g' || colorCode == 'b' ) {
    // increment each color based on number of each letter
    for (int i = 0; i < strlen(serInString); i++) {
      switch(serInString[i]) {
        case 'r':
          redVal += 25;
          break;
        case 'g':
          greenVal += 25;
          break;
        case 'b':
          blueVal += 25;
          break;
      }
    }
    Serial.print("Setting color ");
    Serial.println();
    Serial.print("red to ");
    Serial.print(redVal);
    Serial.println();
    Serial.print("green to ");
    Serial.print(greenVal);
    Serial.println();
    Serial.print("blue to ");
    Serial.print(blueVal);
    Serial.println();
    serInString[0] = 0;                   // indicates we've used this string
    analogWrite(redPin, redVal);
    analogWrite(greenPin, greenVal);
    analogWrite(bluePin, blueVal);
  }
  delay(100);  // wait a bit, for serial data
}
//read a string from the serial and store it in an array
//you must supply the array variable
void readSerialString (char *strArray) {
  int i = 0;
  if(!Serial.available()) {
    return;
  }
  while (Serial.available()) {
    strArray[i] = Serial.read();
    i++;
  }
}
LED color controller