yuvian-arsculpture

spooky skeleton found in my closet

In this project, i toyed with the phrase "skeletons in the closet" and used Unity to make the idiom an (augmented) reality. I've established a relationship between the skeleton and the closet by confining the figure within the space. With its movement, the skeleton looks trapped and anxious - even inquisitive of its surroundings. It glances at the viewer and looks away with secretive movements. I wanted to also make it move out of the closet to symbolize the revealing of one's secrets but was not familiar enough with Unity and animations to implement this idea.

skeleton model

yuvian-book

click here for an example pdf

click here for zip file of all 25 pdfs

"fortune cookies"

randomly generated advertisements for chinese restaurants with their own wacky fortune cookies

demonstrated through a p5js sketch:


Initially, I had no idea what I wanted to do for this project. I was really impressed by the examples we were shown in class and knew that I wanted to incorporate both generative text and generative imagery. In fact, I liked Lingdong's approach in his project "Fauna of Sloogia" where he focused more on generative imagery and the generative text aspect of his book was simply the names he gave to each generated creature.

With this in mind, I first thought of images I could generate. I needed objects that came in large batches/quantities but were not all exactly alike. Items that fit this category included snowflakes, fruits, etc. but these seemed unimaginative. Finally, I thought of fortune cookies and this idea immediately tied into what I wanted to create through generative text - strange fortunes and Chinese restaurant names - and from there, I developed a comprehensive plan for my generative chapter.


Process

This project was split into four parts: the fortune cookie, the takeout box, the restaurant name, and the fortune.

1. The fortune cookie:

For the fortune cookie, I first sketched a few cookies and determined the vertices they all had in common. I gave each cookie 9 vertices and graphed them on a 500x500 p5js canvas.



From there, I played around with bezier curves and shading to create each fortune cookie. Every cookie's vertices and bezier curves are randomized and thus generated by the computer every time.

2. The takeout box:

For the takeout box, I had a very similar approach as I did with the cookie; I sketched takeout boxes, determined the common vertices, and randomized the vertices within p5 so that no two takeout boxes would look alike. In addition, I added text onto the box. Two phrases - comprised of "Thank You" or "Enjoy" - appeared at random points on the box.


3. The restaurant name:
For the generated Chinese restaurant names, I drew inspiration from a discussion I had a few weeks ago about Chinese restaurants commonly being composed of similar words and language.

I put more thought and research into this idea and came up with five categories of words commonly found in Chinese restaurant names: places, adjective, nouns, food, and location. And depending on the length of the name, I would randomly choose one word from each of these categories in a predetermined order and generate names.

places: "Beijing", "Peking", "Szechuan", "Shanghai", "Hunan", "Canton", "Hong Kong", "Taipei", "China", "Taiwan", "Formosa"

adjectives: "Lucky", "Golden", "Gourmet", "Imperial", "Oriental", "Grand", "Mandarin", "Supreme", "Royal", "East", "Old", "Happy", "Hot", "Chinese"

nouns: "Cat", "Moon", "Sun", "Dragon", "Star", "Roll", "Panda", "Bamboo", "Chef", "King", "Empire", "Empress", "Emperor", "Phoenix", "Lion", "Tiger", "Jade", "Pearl"

foods: "Seafood", "Noodle", "Dim Sum", "Hot Pot", "Rice", "Ramen", "Hibachi"

locations: "Palace", "Garden", "Cafe", "Bistro", "Kitchen", "Restaurant", "Buffet", "House", "Wok", "Bowl", "Grill", "Cuisine", "Express"

For example, if I wanted to generate a Chinese restaurant name that was four words long and had a word order of adjective-food-place-location one example would be "Golden Noodle Szechuan Kitchen".

To finalize this, I gave each word length (from two words long to five words long) a chance of 20% (i.e. the chance of the generator returning a three word long name and a five word long name were both 20%). And within each word length, I thought of word orderings and manipulated the chances of each ordering.

To demonstrate the Chinese Restaurant Name Generator in action, click on the following sketch to generate names:


4. The fortune:
To generate the fortune, I first wrote down each fortune with blanks and filled in the blanks with random nouns, adverbs, adjectives, verbs, etc. using RiTa.

code

 
// COOKIE
// points for the cookie
var x1, x2, x3, x4, x5, x6, x7, x8, x9; 
// TAKEOUT BOX
//variables for box points
var tx1,ty1,tx2,ty1,tx3,ty3,tx4,ty4,tx5,ty5,tx6,ty6,tx7,ty7;
//visible flap vertex points
var fx, fy;
//variablesfor handle points
var hx1, hy1, hx2, hy2, hx3, hy3, hx4, hy4;
//RiTA stuff
var rg;
 
var name, lengthChance, typeChance; // variables for generated restaurant name
var myFont; // custom font
var luckyNums = []; // array of lucky numbers
var prediction = ''; // fortune cookie fortune/prediction
var phoneNumber = ''; // phone number
 
function preload() {
    myFont = loadFont('andale-mono.otf');
    chineseFont = loadFont('chinese.ttf')
}
 
function setup() {
  createCanvas(500, 680);
  background(239, 50, 40); // red color
  background(240);
  noLoop();
 
  // assign values to lengthChance and typeChance
  lengthChance = random(0,100);
  typeChance = random(0,100);
 
  // generate restaurant name
  name = generateRestaurantName();
  // display restaurant name text
  displayRestaurantName();
 
  // generate phone number
  generatePhoneNumber();
  drawPhoneNumber();
 
  // draw slip of paper
  drawPaper();
 
  // generate lucky numbers
  generateLuckyNumbers();
  drawLuckyNumbers();
  // display the fortune
  drawFortune();
 
  // generate and draw box
  generateBox();
  drawBox();
 
  // generate cookie 
  generateCookie();
  // draw the cookie
  drawCookie();
 
  prediction = generateFortune();
 
  // button to download json file
  createJSONFile();
 
}
 
function mousePressed() { // generate new cookie, restaurant name, and fortune on mouse press
 setup(); 
}
 
function generateRestaurantName() { // returns string of generated Restaurant name
  name = "";
 
  // Places 11
  var places = ["Beijing", "Peking", "Szechuan", "Shanghai", "Hunan", "Canton", "Hong Kong", "Taipei", "China", "Taiwan", "Formosa"]
  // Adjectives 14
  var adj = ["Lucky", "Golden", "Gourmet", "Imperial", "Oriental", "Grand", "Mandarin", "Supreme", "Royal", "East", "Old", "Happy", "Hot", "Chinese"] 
  // Nouns 18
  var noun = ["Cat", "Moon", "Sun", "Dragon", "Star", "Roll", "Panda", "Bamboo", "Chef", "King", "Empire", "Empress", "Emperor", "Phoenix", "Lion", "Tiger", "Jade", "Pearl"]
  // Food 7
  var food = ["Seafood", "Noodle", "Dim Sum", "Hot Pot", "Rice", "Ramen", "Hibachi"]
  // Last words 13
  var last = ["Palace", "Garden", "Cafe", "Bistro", "Kitchen", "Restaurant", "Buffet", "House", "Wok", "Bowl", "Grill", "Cuisine", "Express"];
 
  // Generate some random names
  if (lengthChance >= 0 && lengthChance <= 25) { // two word length
      if (typeChance >= 0 && typeChance <= 17) {
        name += places [floor(random(11))] + " ";
        name += noun [floor(random(18))];
      }
      else if (typeChance > 17 && typeChance <= 34) {
        name += adj [floor(random(14))] + " ";
        name += noun [floor(random(18))];
      }
      else if (typeChance > 34 && typeChance <= 51) {
        name += food [floor(random(7))] + " ";
        name += last [floor(random(13))];
      }
      else if (typeChance > 51 && typeChance <= 68) {
        name += adj [floor(random(14))] + " ";
        name += last [floor(random(13))];
      }
      else if (typeChance > 68 && typeChance <= 85) {
        name += adj [floor(random(14))] + " ";
        name += food [floor(random(7))];
      }
      else if (typeChance > 85) {
        name += places [floor(random(11))] + " ";
        name += last [floor(random(13))];
      }
 
    } 
    else if (lengthChance > 25 && lengthChance <= 50) { // three word length
      if (typeChance >= 0) {
        name += places [floor(random(11))] + " ";
        name += noun [floor(random(18))] + " ";
        name += last [floor(random(13))];
      }
      else if (typeChance > 17 && typeChance <= 34) {
        name += adj [floor(random(14))] + " ";
        name += noun [floor(random(18))] + " ";
        name += last [floor(random(13))];
      }
      else if (typeChance > 34 && typeChance <= 51) {
        name += adj [floor(random(14))] + " ";
        name += places [floor(random(11))] + " ";
        name += noun [floor(random(18))];
      }
      else if (typeChance > 51 && typeChance <= 68) {
        name += adj [floor(random(14))] + " ";
        name += noun [floor(random(18))] + " ";
        name += food [floor(random(7))];
      }
      else if (typeChance > 68 && typeChance <= 85) {
        name += places [floor(random(11))] + " ";
        name += food [floor(random(7))] + " ";
        name += last [floor(random(13))];
      }
      else if (typeChance > 85 ) {
        name += adj [floor(random(11))] + " ";
        name += food [floor(random(7))] + " ";
        name += last [floor(random(13))];
      }
    } 
    else if (lengthChance > 50 && lengthChance <= 75 ) { // four word length
      if (typeChance >= 0 && typeChance <= 20) {
        name += places [floor(random(11))] + " ";
        name += adj [floor(random(14))] + " ";
        name += noun [floor(random(18))] + " ";
        name += last [floor(random(13))];
      }
      else if (typeChance > 20 && typeChance <= 40) {
        name += places [floor(random(11))] + " ";
        name += adj [floor(random(14))] + " ";
        name += food [floor(random(7))] + " ";
        name += last [floor(random(13))];
      }
      else if (typeChance > 40 && typeChance <= 60) {
        name += adj [floor(random(14))] + " ";
        name += noun [floor(random(18))] + " ";
        name += food [floor(random(7))] + " ";
        name += last [floor(random(13))];
      }
      else if (typeChance > 60 && typeChance <= 80) {
        name += places [floor(random(11))] + " ";
        name += noun [floor(random(18))] + " ";
        name += food [floor(random(7))] + " ";
        name += last [floor(random(13))];
      }
      else if (typeChance > 80) {
        name += adj [floor(random(14))] + " ";
        name += food [floor(random(7))] + " ";
        name += places [floor(random(11))] + " ";
        name += last [floor(random(13))];
      }
    }
    else if (lengthChance > 75) { // five word length
      if (typeChance >= 0 && typeChance <= 40) {
        name += places [floor(random(11))] + " ";
        name += adj [floor(random(14))] + " ";
        name += noun [floor(random(18))] + " ";
        name += food [floor(random(7))] + " ";
        name += last [floor(random(13))];
      }
      else if (typeChance > 40 && typeChance <= 60) {
        name += adj [floor(random(14))] + " ";
        name += food [floor(random(7))] + " ";
        name += noun [floor(random(18))] + " ";
        name += places [floor(random(7))] + " ";
        name += last [floor(random(13))];
      }
      else if (typeChance > 60) {
        name += adj [floor(random(14))] + " ";
        name += noun [floor(random(18))] + " ";
        name += places [floor(random(11))] + " ";
        name += food [floor(random(7))] + " ";
        name += last [floor(random(13))];
      }
    }
    return name;
}
 
function displayRestaurantName() {
  textAlign(LEFT);
  textFont(myFont);
  fill(0);
  textSize(12);
	noStroke();
  text(name, 40, 50);
}
 
function generateCookie() { // generate points for the cookie
  // generate random points for fortune cookie
  x1 = width/2 - random(15,25);
  y1 = random(50,80);
  x2 = random(60, 78);
  y2 = random(225, 245);
  x3 = x2 - random(10,15);
  y3 = random(360, 380);
  x4 = 260;
  y4 = random(420,440);
  x5 = 255;
  y5 = random(300,350);
  x6 = 252;
  y6 = y5 - 65;
  x7 = 250;
  y7 = random(140,160);
  x8 = random(380, 390);
  y8 = random(390, 420);
  x9 = x8 + random(20,35);
  y9 = y2 - 20;
 
  // bezier vertices
  b1x = x1 - random(90,130);
  b1y = y1 + random(8,13);
  b2x = x2 + random(40,50);
  b2y = y2 - random(40,60);
  b3x = x5 + random(5, 8);
  b3y = (y5 - y6) * 0.9 + y6
  b4x = x6 - random(25,35);
  b4y = random(155,170);
  b5x = x7 + random(15,20);
  b5y = y7 + random(20,30);
  b6x = x6 + random(5,15);
  b6y = y6 - random(25,40);
}
 
function drawCookie() {
  push();
  translate(80,280);
  scale(0.35);
 
  stroke(0);
  // fill(247, 237, 185); // beige color
  fill(255);
  strokeWeight(4);
 
  // left half of cookie
  beginShape();
  vertex(x1, y1);
  bezierVertex(b1x, b1y, b2x, b2y, x2, y2);
  vertex(x2, y2);
  bezierVertex(x2,y2,x3,y3,x4,y4);
  vertex(x4, y4);
  vertex(x5, y5);
  bezierVertex(x5, y5, b3x, b3y, x6,y6);
  endShape();
 
  // right half of cookie
  beginShape();
  vertex(x5,y5);
  bezierVertex(x5,y5,(x5+x8)/2 - 10,(y5+y8)/2 + 10,x8,y8);
  vertex(x8, y8);
  bezierVertex(x8,y8, (x9-x8)*1.4+x8, (y8 - y9) * 0.9 + y9 ,x9,y9);
  vertex(x9, y9);
  bezierVertex(x9-80, y9 -50, x1 + 90, y1 + 20, x1, y1);
  vertex(x1, y1);
  endShape();
 
  // inner crease
  beginShape();
  fill(0);
  vertex(x6,y6);
  bezierVertex(x6,x6,b4x,b4y,x7,y7);
  vertex(x7,y7);
  bezierVertex(b5x,b5y,b6x,b6y,x6,y6);
  vertex(x6,y6);
  endShape();
 
  // left fold of cookie
  fill(0);
  beginShape();
  vertex(x2, y2);
  bezierVertex(x2,y2,x3,y3,x4,y4);
  vertex(x4, y4);
  bezierVertex(x4, y4, x4-20, y4-35, (x2+x4)/2 - 15, (y2+y4)/2 + 15); 
  bezierVertex((x2+x4)/2 - 15, (y2+y4)/2 + 15, x2 +10, y2 +80, x2, y2);
  vertex(x2,y2);
  endShape();
 
  // line connecting inner black ellipse and left vertex
  strokeWeight(3);
  line(x6,y6,x5,y5);
 
  pop();
}
 
function generatePrediction() {
 
}
 
function generateLuckyNumbers() {
  for (var i = 0 ; i < 6; i++) {
    luckyNums[i] = String(floor(random(0,100)));
  }
  return luckyNums;
}
 
function drawLuckyNumbers() {
  luckynum1 = luckyNums[0];
  luckynum2 = luckyNums[1];
  luckynum3 = luckyNums[2];
  luckynum4 = luckyNums[3];
  luckynum5 = luckyNums[4];
  luckynum6 = luckyNums[5];
 
  fill(0);
  textSize(12);
  textFont(myFont);
	noStroke();
	textAlign(CENTER);
  text("Lucky Numbers: " + luckynum1 + "  " + luckynum2 + "  " + luckynum3 + "  " + luckynum4 + "  " + luckynum5 + "  " + luckynum6, width/2, 545);
}
 
function generatePhoneNumber() {
  phoneNumber = "(" + String(floor(random(1,10))) + String(floor(random(0,10))) + String(floor(random(0,10))) + ")-" + String(floor(random(0,10))) + String(floor(random(0,10))) + String(floor(random(0,10))) + "-" + String(floor(random(0,10))) + String(floor(random(0,10))) + String(floor(random(0,10))) + String(floor(random(0,10)))
}
 
function drawPhoneNumber() {
  textAlign(LEFT);
  textFont(myFont);
  fill(0);
  textSize(12);
	noStroke();
  text("Call " + phoneNumber + " to order!", 40, 80);
}
 
function generateBox() {
  // box points
  tx1 = random(100,140);
  ty1 = random(150,190);
  tx2 = random(140,180);
  ty2 = random(430,500);
  tx3 = random(250,300);
  ty3 = ty2 + random(80,100);
  tx4 = random(480,510);
  ty4 = random(470,500);
  tx5 = random(500,530);
  ty5 = random(140,180);
  tx6 = random(320,360);
  ty6 = random(90,120);
  tx7 = tx3;
  ty7 = ty1 + random(40,60);
 
  //flap points
  fx = random((tx1 + 45)-15, (tx1 + 45) + 15);
  fy = random((ty1 + 120) - 10, (ty1 + 120) + 10);
 
  //handle points
  hx1 = floor((tx1 + tx7) / 2)
  hy1 = floor((ty1 + ty7) / 2) + 40;
  hx2 = hx1 - random(3,7);
  hy2 = hy1 - random(120,160);
  hx4 = floor((tx6 + tx5)/2)
  hy4 = floor((ty6 + ty5)/2)
  hx3 = floor((tx6 + tx5)/2)
  hy3 = hy4 - random(70,100);
}
 
function drawBox() {
  push();
  scale(0.5);
  translate(260,300);
  fill(255);
  strokeWeight(2.6);
  stroke(0);
 
  //left face
  beginShape();
  vertex(tx1, ty1);
  vertex(tx2, ty2);
  vertex(tx3, ty3);
  vertex(tx7, ty7);
  vertex(tx1, ty1);
  endShape();
 
  //right face
  beginShape();
  vertex(tx3,ty3);
  vertex(tx4,ty4);
  vertex(tx5, ty5);
  vertex(tx7, ty7);
  vertex(tx3,ty3);
  endShape();
 
  //left folds
  //back flap
  beginShape();
  vertex(tx1,ty1);
  vertex(235,310);
  vertex(tx2, ty2);
  vertex(tx1, ty1);
  endShape();
  //front flap
  beginShape();
  vertex(tx7,ty7);
  vertex(fx,fy);
  vertex(tx3, ty3);
  vertex(tx3, ty3);
  vertex(tx7, ty7);
  endShape();
 
  //top face
  beginShape();
  vertex(tx5,ty5);
  vertex(tx6,ty6);
  vertex(tx1,ty1);
  vertex(tx7,ty7);
  vertex(tx5,ty5);
  endShape();
 
  //handle
  //left vertical
  strokeWeight(3);
  // line(hx1,hy1,hx2,hy2);
  beginShape();
  noFill();
  vertex(hx1, hy1);
  vertex(hx2,hy2+20);
  bezierVertex(hx2, hy2+20, hx2, hy2, hx2+20, hy2)
  vertex(hx2+20, hy2)
  vertex(hx3 - 20,hy3)
  bezierVertex(hx3-20, hy3, hx3, hy3, hx3, hy3+20)
  vertex(hx3, hy3+20)
  vertex(hx4, hy4)
  endShape();
  //top horizontal
  // line(hx2,hy2,hx3,hy3);
 
  drawMessages();
 
  pop();
}
 
function drawMessages() {
  var messages = ["ENJOY", "THANK YOU"];
  var i1 = floor(random(2));
  var i2 = floor(random(2));
  var m1 = messages[i1];
  var m2 = messages[i2];
  textFont(chineseFont);
  noStroke();
  fill(0);
  textAlign(CENTER);
  textSize(25);
 
  x1 = floor((tx5-tx7)/2 + random(200,270));
  y1 = floor((ty4-ty3)/2) + random(250,300);
  x2 = x1 + random(60,90);
  x2 = constrain(x2, tx1, tx4);
  y2 = y1 + random(90,140);
  y2 = constrain(y2, ty7, ty4);
  r1 = random(HALF_PI/10,HALF_PI/6)
  r2 = random(-HALF_PI/10, HALF_PI/10)
 
  push();
    rotate(r1);
    text(m1, x1, y1);
  pop();
 
  push();
    rotate(r2);
    text(m2, x2, y2);
  pop();
}
 
function drawPaper() {
  rectMode(CENTER)
  stroke(0);
  fill(210);
  rect(width/2, 525, 480, 90);
}
 
function generateFortune() {
  rg = new RiGrammar();
 
  //baseline for fortune cookie fortunes
  rg.addRule('<start>', 'Whoever <V-Singular-Present> a <N-Singular> will never be <V-Past> \nby a <N-Singular>.', 1);
  rg.addRule('<start>', 'Life is too short to <V-Plural-Present> <N-Plural>.', 1);
  rg.addRule('<start>', 'Your greatest strength is that you are <Adjective>.', 1);
  rg.addRule('<start>', 'Your future seems <Adverb> <Adjective>.', 1);
  rg.addRule('<start>', 'Alas, life is but a <Adjective> <N-Singular>.', 1);
  rg.addRule('<start>', 'Your <N-Singular> shines on another.', 1);
  rg.addRule('<start>', 'You will overcome <Adjective> <N-Plural>.', 1);
  rg.addRule('<start>', 'It is not necessary to <V-Plural-Present> others your <N-Singular>; \nit will be obvious.', 1);
  rg.addRule('<start>', 'Sometimes you just need to <V-Plural-Present> the <N-Singular>.', 1);
  rg.addRule('<start>', 'See if you can <V-Plural-Present> anything from the <N-Plural>.', 1);
  rg.addRule('<start>', 'Make the <N-Singular> <V-Plural-Present> for you, not the other way around.', 1);
  rg.addRule('<start>', 'In the eyes of <N-Plural>, everything is <Adjective>.', 1);
  rg.addRule('<start>', 'People in your surroundings will be more <Adjective> than usual.', 1);
  rg.addRule('<start>', 'You will be successful at <V-ing> <N-Plural>.', 1);
  rg.addRule('<start>', 'Whenever possible, keep it <Adjective>.', 1);
  // rg.addRule('<start>', '', 1);
 
  var args1 = {
    tense: RiTa.PRESENT_TENSE,
    number: RiTa.SINGULAR,
    person: RiTa.THIRD_PERSON
  };
  var args2 = {
    tense: RiTa.PRESENT_TENSE,
    number: RiTa.PLURAL,
    person: RiTa.THIRD_PERSON
  };
  var args3 = {
    tense: RiTa.PAST_TENSE,
    number: RiTa.SINGULAR,
    person: RiTa.THIRD_PERSON
  };
 
  //nouns
  rg.addRule('<N-Singular>', RiTa.randomWord("nn"));
  rg.addRule('<N-Plural>', RiTa.randomWord('nns'))
 
  //verbs
  var v = RiTa.randomWord('vb');
  rg.addRule('<V-Singular-Present>', RiTa.conjugate(v, args1));
  rg.addRule('<V-Plural-Present>', RiTa.conjugate(v, args2));
  rg.addRule('<V-Past>', RiTa.conjugate(v, args3));
  rg.addRule('<V-ing>', RiTa.randomWord('vbg'));
 
  //adjective
  rg.addRule('<Adjective>', RiTa.randomWord('jj'));
 
  //adverb
  rg.addRule('<Adverb>', RiTa.randomWord('rb'));
 
  //preposition
  // rg.addRule('<Preposition>', RiTa.randomWord('in'));
 
  result = rg.expand();
  return result;
}
 
function drawFortune() {
  fill(0);
  textSize(12);
  textFont(myFont);
	noStroke();
	textAlign(CENTER);
  text(prediction, width/2, 515);
}
 
function createJSONFile() {
  // Create a JSON Object, fill it with the restaurants.
  var myJsonObject = {};
  myJsonObject.restaurantName = name;
  myJsonObject.phoneNumber = phoneNumber;
  myJsonObject.prediction = prediction;
  myJsonObject.luckyNumbers = luckyNums;
 
  // Make a button. When you press it, it will save the JSON file
  createButton('save')
    .position(width/2-20, height-50)
    .mousePressed(function() {
      saveJSON(myJsonObject, 'data.json');
    });
}

yuvian-sepho-Automaton

The idea for this project came gradually. After compiling bags of random objects from Pittsburgh's Center for Creative Reuse, Joe and I were initially drawn to the discarded doll parts. Originally, we wanted to work with the doll head as it captured the "creepy" factor we were going for, but the idea of a physical interaction with the automaton came up in the form of a high-five. Additionally, the idea of a creepy doll head seemed overused at the time so we decided against it. Thus, we ran with the high-five idea - utilizing multiple hands and arms to create a machine that started with a simple high-five.

Regarding our respective roles in this project, I worked on a lot of the hardware + crafty end of things while Joe contributed a lot to the coding of the project as he had some prior experience with Arduino. The idea and design for this project was pretty much an equally collaborative aspect as we finalized decisions through conversations we had together.

In order to interact with our automaton, aptly named "Handy Warhol", a user must initiate a high-five with it, which then triggers the machine to high-five itself and generate noise.

yuvian-Body

flappy face

note: this is not working 100% smoothly yet (collision detection is buggy sometimes)

For this project, I worked with clmtrackr.js in order to take input from the user's face to control a game. More specifically, I took note of where the user's face was positioned within the frame and the width of their smile. The more the user smiles, the higher the red "bird" flies. Moving his/her face from side to side also changes the bird's x-position on the canvas. I developed this project by getting clmtrackr to work first, then pulling data from the array of points it had given me, and then styling and implementing the game aspect. I didn't want to work from a motion capture because I wanted it to be more interactive, which is why I chose to get the user's face from his/her webcam. But with this also comes the potential for buggy face tracking as I have found while testing the project. I chose a flappy bird-esque game to implement because I wanted the game to be simple, easily understood, and somewhat recognizable. I had made another duplicate side-project in which the "bird" goes around eating berries but it seemed less like a game and more like a task.

how to play:
- smile widely to move bird up
- move head side to side to move bird left and right
- avoid white blocks

smile

open mouth

var ctracker;
var video;
var positions;
var bird;
var blockSpeed = 5
var blocks = [];
var score = 0;
var startGame = false;

function setup() {

  // setup webcam video capture
  video = createCapture(VIDEO);
  video.size(600, 450);
  video.position(0, 0);

  //hide video feed
  video.hide();

  // setup canvas
  createCanvas(600, 450);

  // setup tracker
  ctracker = new clm.tracker();
  ctracker.init(pModel);
  ctracker.start(video.elt);

  noStroke();

  // add block to blocks array
  for (var i = 0; i < 1; i++) {
    blocks[i] = new Block(random(40, 100), random(50, 100))
  }

}

function draw() {
  
  background(0);
  
  push();
  translate(video.width, 0);
  scale(-1, 1);

  image(video, 0, 0, width, height)

  positions = ctracker.getCurrentPosition();
  for (var i = 0; i < positions.length; i++) {
    fill(255, 80);
    // for when clmtracker is being weird in p5js editor
    // ellipse(positions[i][0] - 20, positions[i][1] - 10, 4, 4);
    // for online p5js editor
    ellipse(positions[i][0], positions[i][1], 2.5, 2.5);
  }
  pop();
  
  if (!startGame) {
    textAlign(CENTER)
    fill(255);
    text("press the space bar to start", width/2, height/2);
  }

  // if face is found, commence the game
  if (positions.length > 0 && startGame) {
    playGame()
  }
}

function keyPressed() {
  if (key == " ") {
    startGame = true
  } 
  else if (key == "R") {
    print('restart')
    setup();
  }
}


function playGame() {
    // blocks
    for (var i = 0; i < blocks.length; i++) {
      blocks[i].move();
      blocks[i].display();

      // remove block when it goes off screen and add a new block
      if (blocks[i].x < 25) {
        score += 1;
        blocks = []
        blocks.push(new Block(random(40, 100), random(50, 300)))
        // gradually increase speed of the moving blocks
        if (blockSpeed < 13) {
          blockSpeed = blockSpeed * 1.05
        } else {
          blockSpeed = blockSpeed * 1.005
        }
      }
  
      push();
        // display score
        fill(0);
        rectMode(CENTER);
        rect(80, 47, 70, 25)
        fill(255);
        textAlign(CENTER);
        text('score: ' + score, 80, 50);
      pop();
    }

    // background of smile bar
    fill(255);
    rect(20, 20, 15, height - 40);

    // smile bar
    var mouthLeft = createVector(positions[44][0], positions[44][1]);
    var mouthRight = createVector(positions[50][0], positions[50][1]);
    var smileDistance = Math.round(mouthLeft.dist(mouthRight));

    smile = map(smileDistance, 60, 75, 17, 0); // mapped smile distance to chopped up parts so bird doesn't jitter as much
    
    // prevent smile bar from going beyond bottom of bar
    rectSmile = smile * 20
    rectSmile = constrain(rectSmile, 20, height - 20)
    fill(50);
    rect(20, 20, 15, rectSmile);

    // smile number next to smile bar
    fill(255);
    smileText = height - 40 - Math.round(smile)
    // text(smileText, 50, constrain(smile, 30, height - 20))

    // bird
    push();
    translate(video.width, 0);
    scale(-1, 1);
    var noseX = positions[37][0]; // get nose x position to determine center of face
    noseX = noseX - 25 // wack p5js editor positioning
    birdY = smile * 20
    birdY = constrain(smile * 20, 50, height - 50); //prevent bird from moving offscreen
    bird = new Bird(noseX, birdY); // bird's x is nose position, y depends on width of smile
    bird.display();
    pop();
    
    // if bird collides with block, freeze everything and display game over message
    // for (var i = 0; i < blocks.length; i++) {
    block = blocks[0]
    if (bird.x >= block.x  && bird.x <= block.x + block.width) {
      bottomHeight = height - block.opening - block.height
      if (bird.y <= block.height || bird.y >= bottomHeight) {
        print('collision')
        // freeze webcam video, blocks, and bird
        video.stop()
        block.speed = 0
        // bird.y = height/2
        gameOver();
      }
    }
    
    // check top collision,
    // count = 0
    // d = getDistance(block.x, block.height, bird.x, bird.y)
    // if ( d < block.width/2) {
    //   count += 1
    //   print('hit' +  count)
    // }
  
    
    // alert user to fix positioning if bird is too high or too low
    if (smileText < 0) {
      fill(226, 74, 92);
      strokeWeight(3);
      text('move closer', 80, 100);
    }
    else if (smileText > 900) {
      fill(226, 74, 92);
      strokeWeight(3);
      text('move farther away', 80, 100);
    }
}

function gameOver() {
  fill(255);
  text("GAME OVER", width/2, height/2)
}

function Bird(x, y) {
  this.x = x;
  this.y = y;

  this.display = function() {
    fill(226, 74, 92);
    ellipse(this.x, this.y, 25, 25);
  }
}

function Block(w, h) {
  this.x = width - w / 2;
  this.width = w;
  this.height = h;
  this.speed = blockSpeed;
  this.opening = random(90, height / 2 - 50);

  this.display = function() {
    fill(255);
    // top block
    rect(this.x, 0, this.width, this.height)
      // bottom block
    rect(this.x, this.height + this.opening, this.width, height - this.opening - this.height)
  }

  this.move = function() {
    this.x = this.x - this.speed
  }
}

// from https://gist.github.com/timohausmann/5003280
function getDistance(x1,y1,x2,y2) {
  var 	xs = x2 - x1,
	ys = y2 - y1;		
	xs *= xs;
	ys *= ys;
	return Math.sqrt( xs + ys );
}

yuvian-viewing04

When Wharburton refers to a work as a "spectacle", he implies that the project may be technically astounding and visually interesting but is lacking in content and self-awareness.

Conversely, computer art that is "speculation" deals heavily with conceptual and theory-based ideas but may not have strong technical applications.

Studio Moniker's "Do Not Touch" (2013) is an example of a "spectacle". Visually, the project is interesting and compelling to look at while the video plays but substance-wise, there's not much to be said about the concept. Using the dichotomies from Wharburton's video, "Do Not Touch" leans more towards commerce than art as it is a music video intended to sell copies of the band's albums. Furthermore, it does nothing to increase awareness/visibility of pressing social issues. The only compelling aspect of the project is its "interactivity" with the band's fans. One could argue that the tracking of the users' mice allow us to visualize the obedient and disobedient tendencies of the users. But not much else can be said about the work as a whole.

yuvian-telematic

click here to play

note: webcam does not work in the embedded iframe below. please click the above link to play in glitch

Trace/sketch from your webcam. See webcam sketches from other people. Can you understand where they are, what they're doing, what they look like from their tracings?

So this project turned out a lot different than what I originally had in mind. First, I played around with clmtrackr.js because I wanted to make a multi-user sketching game where clmtrackr would generate a very simplified outline of a user's face from their webcam feed and other users would be able to add features such as facial hair, hairstyles, etc. That didn't work out too great because although I got clmtrackr.js to work in p5, I couldn't figure out how to draw on it. So then, I decided to retain the webcam input idea but without facial tracking. Eventually, it got to the point where it was very simplified - use the webcam feed to capture snapshots of the user's environment, ask them to trace/sketch over it, and show that sketch within a shared environment. I wanted the webcam snapshot to only be seen by its user because I wanted to retain some anonymity. The purpose of tracing/sketching over the webcam is to simulate a video call while forcing users to rely on their creativity and artistic ability to "show" themselves rather than reveal everything about them from live video. Because of this, this project is synchronous, because it requires users to be active and drawing at the same time. It's pretty anonymous because although you're trying to represent yourself through tracings of your image, it's unlikely that other users will be able to tell who you really are. In addition, you can manipulate your features at will.

yuvian-LookingOutwards02

Strata #3 - Excerpt from Quayola on Vimeo.

"Strata #3" is a film and installation project commissioned by Evento and Lumin for the 2009 Bordeaux Art & Architecture Biennale. Directed by Quayola, the project utilizes the aesthetics of classical architecture and Renaissance art combined with generative patterns and technological analysis of the original artwork in order to create a psychedelic and fragmented artistic experience.

One thing that inspired me is the creative use of "old" art and new technologies in one project. I love how the project feels familiar and yet completely new at the same time. The music in the video also pairs very well with the artistic direction of the film. The visuals are stunning and the concept itself is brilliant. I'm not sure how this would have looked in real-life as it was mentioned that this is an installation as well, but from the video, it is amazing to look at.

yuvian-Reading03

I found Naimark's short essay on "first word art" and "last word art" interesting and novel as I had never previously considered why it mattered when artworks are deemed experimental and new or just a continuation within well-known territory. Personally, I'm not quite sure where my interests lie along this spectrum. I think it's daring to create something entirely new and although it may be exciting, the fact that nothing else like it exists seems to pose a challenge within its creation. To me, it seems that the world has been inundated with "new" ideas and paradoxically, nothing seems truly "new" anymore. Although the author mentions that some believe that "last word art" must not really be art because it is nothing never-before-seen, I would have to disagree because I think artists can always find ways to evolve an existing idea, adapting to our ever-changing environments. Our society constantly demands artists and designers to create something novel yet this request is nothing new. Quick, bold ideas that change every quarter seem to be most desirable within technology whereas the art world generally likes to cling onto the work of the past. Thus the middle-ground between these two worlds must be an environment in which we are heavily influenced by past ideas while at the same time, being driven to create something entirely new.

yuvian-clock

new clock

For my first clock, I chose to represent time in a way similar to that of a normal clock. With a familiar elliptical shape, I used three rings to represent the hour, minute, and second. According to the time, I placed dots along the three rings which are evenly divided between the 360 degrees of a circle. Because it may get hard to read at times, I also added a feature that would allow the user to see the actual time when they move their mouse over the center of the clock. Afterwards, I added some styling to the clock - namely, the grey ring and circle to help differentiate between hours, minutes, and seconds.

//60212 CLOCK SKETCH

var h, m, s;

function setup() {
  createCanvas(500,500);
}

function draw() {
  background(0);
  
  h = hour();
  m = minute();
  s = second();
  
  ellipseMode(CENTER);
  
  //white circle bg for hours
  stroke(90);
  noFill();
  strokeWeight(15)
  ellipse(width/2,height/2, 380)
  
  
  //grey circle bg for seconds
  fill(30);
  noStroke();
  ellipse(width/2,height/2, 200,200)
  
  if (mouseX > 200 && mouseX < 300 && mouseY > 200 && mouseY < 300) {
    displayTime();
  }
  
  push();
    translate(width/2, height/2);
    angleMode(DEGREES)
    rotate(-90)
    dotSize = 3;
    noStroke();
    ///////////////////////////////////
    var hRad = 190;
    // var hColor = map(h,0,23,50,255);
    var hColor = color(0)
    // var hColor = color(226, 9, 63)
    var hDotSize = 5;
    fill(hColor);
    for (var i = 0; i <= 360; i+= 360/h) {
			if (h == 0) {
				h = 12
			} else {
				ellipse(cos(i) * hRad, sin(i) * hRad,hDotSize,hDotSize);
			}
      
    }
    ///////////////////////////////////
    var mRad = 130;
    var mColor = map(m,0,59,50,255);
    // var mColor = color(53, 87, 255)
    fill(mColor);
    for (var i = 0; i <= 360; i+= 360/m) {
      if (m > 0) {
        ellipse(cos(i) * mRad, sin(i) * mRad,dotSize,dotSize);
      }
    }
    ///////////////////////////////////
    var sRad= 90;
    var sColor = map(s,0,59,50,255);
    // var sColor = color(255, 86, 128)
    fill(sColor)
    for (var i = 0; i <= 360; i+= 360/s) {
      if (s > 0) {
        ellipse(cos(i) * sRad, sin(i) * sRad,dotSize,dotSize);
      }
    }
    ///////////////////////////////////
  pop();
}

function displayTime() {
  noStroke();
  fill(255);
  textAlign(CENTER);
  textSize(15);
  // xTime = 60
  // yTime = 40
  xTime = width/2;
  yTime = height/2;
	
	if (h==0) {
		h = 12
	}
  
	//display zero in front of single digit second
  if (s < 10 && m >= 10) {
    text(h + " : " + m + " : 0" + s, xTime, yTime);
	}
	//display zero in front of single digit minute
  else if (s >= 10 && m < 10) {
    text(h + " : 0" + m + " : " + s, xTime, yTime);
	}
	//display zeroes in front of single digit seconds and minutes
  else if (s < 10 && m < 10) {
    text(h + " : 0" +m + " : 0" + s, xTime, yTime);
	}
	//keep seconds and minutes as is when two digits
  else {
    text(h + " : " + m + " : " + s, xTime, yTime);
	}
}

face clock


Now this clock is much more interesting to look at. For this project, I altered another clock I had previously made. In this sketch, I incorporated color, animation (somewhat), and gave this "clock" a lot more personality.

var hr, mn, sc;
var end_hr, end_mn, end_sc;

function setup() {
 createCanvas(400,400); 
}

function draw() {
  background(255);
  
  hr = hour();
  mn = minute();
  sc = second();
  end_hr = map(hr % 12,0,12,0,360);
  end_mn = map(mn, 0, 60, 0, 360);
  end_sc = map(sc, 0, 60, 0, 360);
  
  /////////// time /////////////////////////
  
  //Display time
  if (sc < 10) {
    noStroke();
    fill(135, 158, 163);
    textAlign(CENTER);
    textSize(20);
    strokeWeight(5);
    text(hr + " : " + mn + " : 0" + sc, 80, 30);
  } else {
    noStroke();
    fill(135, 158, 163);
    textAlign(CENTER);
    textSize(20);
    strokeWeight(5);
    text(hr + " : " + mn + " : " + sc, 80, 30);
  }
  
  push();
  strokeWeight(8);
  noFill();
  angleMode(DEGREES);
  translate(width/2,height/2);
  rotate(-90);
  //seconds - innermost
  stroke(164, 206, 216);
  arc(0, 0, 250, 250, 0, end_sc);
	
  //minutes - middle
  stroke(35, 126, 196); 
  arc(0, 0, 320, 320, 0, end_mn);

  //hours - outside
  stroke(31, 56, 119); 
  arc(0,0,345,345,0,end_hr);
  pop();
  
  ///////////// static face ///////////////
  push();
  angleMode(DEGREES);
  translate(width/2,height/2);
  rotate(-90);
  
  //Top of face
  stroke(0);
  strokeWeight(7);
  noFill();
  arc(0,0,280,280,280,80);
  
  //Bottom of face
  noFill();
  arc(0,0,300,300,150,210);
  pop();

  //black eyebrows 
  stroke(0);
  fill(0);
  rectMode(CENTER);
  rect(130, 150, 25, 5, 15);
  rect(270, 150, 25, 5, 15);
  
  //eyes
  ellipseMode(CENTER);
  ellipse(120, 200, 20, 20);
  ellipse(280, 200, 20, 20);
  
  //nose
  noStroke();
  strokeWeight(6);
  fill(204, 95, 95);
  rect(width/2, 230, 15, 30, 20);
  
  //lips
  stroke(0);
  noFill();
  rect(width/2, 280, 60, 20, 20);
  
  //teeth
  line(width/2, 270, width/2, 290);
  line(width/2 - 15, 270, width/2 - 15, 290);
  line(width/2 + 15, 270, width/2 + 15, 290);
  
  ///////// face animations ///////////////
  
  if (sc % 2 == 0) {
    //eyebrows raise
    //cover old eyebrows w white
      stroke(255);
      strokeWeight(8);
      rectMode(CENTER);
      rect(130, 150, 25, 5, 15);
      rect(270, 150, 25, 5, 15);
    //new raised eyebrows
      strokeWeight(5);
      stroke(0);
      rect(130, 140, 25, 5, 15);
      rect(270, 140, 25, 5, 15);
    
    //mouth becomes smaller
    //cover old mouth
      //lips
      strokeWeight(10);
      stroke(255);
      noFill();
      rect(width/2, 280, 60, 20, 20);
      //teeth
      line(width/2, 270, width/2, 290);
      line(width/2 - 15, 270, width/2 - 15, 290);
      line(width/2 + 15, 270, width/2 + 15, 290);
    //new small mouth
      strokeWeight(6);
      stroke(0);
      ellipse(width/2, 280, 20,20);
	}
}

yuvian-Scope

download design as pdf here

For my praxinoscope design, I chose to keep it simple. In order to create this sketch, I first conducted research on bezier vertices and objects in Java and learned how to implement them in this project. In this praxinoscope, I used two, pulsing hearts - inspired by this heart emoji because it's one of my favorite emojis ¯\_(ツ)_/¯.


void drawArtFrame (int whichFrame) { 

  // Draw the frame number
  fill(0); 
  noStroke(); 
  textAlign(CENTER, CENTER); 
  text (whichFrame, -1, -47);

  // Draw expanding double heart emojis
  int nHearts = 3;
  for (int i=0; i