paukparl-clock

Brief:
I thought of a digital clock with each digit restlessly trying to stand still, while continuously having to offset external forces to stay balanced. I think such state is called dynamic stability. I was inspired by Jacob Tonski's Balance From Within. I used processing with geomerative and fisica libraries.

Rules:
1. Every time a digit needs to update, it disappears and falls again from a fixed position. Same rule when a digit goes out of bounds. This can cause some unexpected chain reactions in the following few seconds.
2. Each digit has an inherent "tremble" generated by noise. The digit would try to offset the torque by shifting its body left or right. But once it falls to its side, it will be hard to get back up again. Sadly, it will stay there until it needs update and a new number takes its place.


when the clock first turns on


some tension from 8


I like how 0 falls on the 5.

Thoughts:
I think my current code cannot perfectly offset the forces. It could be more interesting if the numbers stayed on their feet longer. Also, I first imagined the numbers kind of bouncing left and right micro-scale (like a person does on one foot) and bumping into one another, but couldn't implement that in time. But I can still somewhat see how the numbers are reluctant to fall on its side, and I like that.

import fisica.*;
import geomerative.*;
 
FWorld world;
RFont font;
FBody[] bodies;
 
int FONT_SIZE = 200;
 
int lastShiftTime; 
int newShiftInterval;
float randomShift;
boolean bRecording = false;
int lastMinute;
 
boolean[] needToCheckUpdate = {true, true, true, true};
 
FCharController[] controllers;
FChar[] colon;
 
void setup() {
  size(640,200);
  smooth(8);
 
  frameRate(60);
 
  Fisica.init(this);
  Fisica.setScale(4);
  RG.init(this);
 
  RG.setPolygonizer(RG.ADAPTATIVE);
 
  world = new FWorld();
 
  randomShift = random(-50, 50);
  world.setGravity( 0, 400 );
  newShiftInterval= int(random(2000,8000));
  world.setEdges(0, 0, width, height+5, color(255, 0, 0)); 
  world.setEdgesFriction(1); 
  world.remove(world.top);
  world.remove(world.left);
  world.remove(world.right);
 
  font = RG.loadFont("cheltenham-cond-normal-300.ttf");
 
  colon = new FChar[2];
  colon[0] = new FChar('.', width/2-15, 142);
  colon[1] = new FChar('.', width/2-15, 75);
  world.add(colon[0]);
  world.add(colon[1]);
 
  controllers = new FCharController[4];
  for (int i=0; i<controllers.length; i++) {
    FCharController controller = new FCharController(i);
    controller.dropNewCharObj();
    controllers[i] = controller;
  }  
  lastMinute = minute();
}
 
void draw() {
  checkMinutePassed();
  for (int i=0; i<controllers.length; i++) controllers[i].update();
  background(0);
  world.draw(this);
  world.step();  
}
 
 
void checkMinutePassed() {
  if (minute() == lastMinute) return;
  for (int i=0; i<needToCheckUpdate.length; i++) needToCheckUpdate[i] = true; lastMinute = minute(); } class FCharController { FChar charObj; int index; int lastNumber; boolean disappeared; int dropX; float shiver; float xoff; FCharController(int index) { //charObj = new FChar('9', 100, 150); this.index = index; disappeared = false; lastNumber = 0; // doesn't matter what I assign here dropX = index*150+50; if (index==1) dropX -= 20; else if (index==2) dropX += 20; } void dropNewCharObj() { needToCheckUpdate[index] = false; int newNumber = parseTime(index); println((char)(newNumber+48)); charObj = new FChar((char)(newNumber+48), dropX, -50); world.add(charObj); lastNumber = newNumber; } void dropNewCharObj(int test) { needToCheckUpdate[index] = false; int newNumber = test; println((char)(newNumber+48)); charObj = new FChar((char)(newNumber+48), dropX, -50); //charObj.setRotation(0.115*PI); world.add(charObj); } void update() { if(charObj.isTouchingBody(world.bottom)) { shiver = noise(xoff+=0.025); shiver*=10; shiver-=5; charObj.addTorque(shiver*10000); charObj.addForce(shiver*-10000, 0, 40, 0); } if (charObj.getY()>230) {
      charObj.removeFromWorld();
      dropNewCharObj();
      xoff = random(100);
    }
    if (needToCheckUpdate[index] == true) {
      if (lastNumber == parseTime(index)) return;
      int newNumber = parseTime(index);
      charObj.removeFromWorld();
      dropNewCharObj();
      lastNumber = newNumber;
      xoff = random(100);
    }
  }
 
  int parseTime(int index) {
    switch (index){
      case 0: return hour()/10;
      case 1: return hour()%10;
      case 2: return minute()/10;
      case 3: return minute()%10;
    }
    return 9;
  }
 
}
 
 
class FChar extends FPoly {
  RShape m_shape;
  RShape m_poly;
  boolean m_bodyCreated;
  float initialAngle=0;
 
  FChar(char chr, float dropX, float dropY){
    super();
 
    polygonize(chr);
    setInitialAngle(chr);
 
    this.setFill(0, 0, 0);
    this.setStroke(255);
    if (chr=='.') {
      this.setDamping(1);
      this.setRestitution(0.2);
      this.setBullet(true);
      this.setStaticBody(true);
      this.setPosition(dropX, dropY);
    }
    else {
      this.setDamping(1);
      this.setRestitution(0.2);
      this.setBullet(true);
      this.setPosition(dropX, dropY);
      this.setAllowSleeping(true);
      this.setRotation(initialAngle);
    }
 
 
    m_bodyCreated = true;
  }
 
 
  void polygonize(char chr) {
    String txt = "";
    txt += chr;
 
    RG.textFont(font, FONT_SIZE);
    m_shape = RG.getText(txt);
    m_poly = RG.polygonize(m_shape);
 
    if (m_poly.countChildren() < 1) return;
    m_poly = m_poly.children[0];    
 
    // Find the longest contour of our letter    `  
    float maxLength = 0.0;
    int maxIndex = -1;
    for (int i = 0; i < m_poly.countPaths(); i++) { float currentLength = m_poly.paths[i].getCurveLength(); if (currentLength > maxLength) {
        maxLength = currentLength;
        maxIndex = i;
      }
    }
 
    if (maxIndex == -1) return;
 
    RPoint[] points = m_poly.paths[maxIndex].getPoints();
 
    for (int i=0; i<points.length; i++) {
      this.vertex(points[i].x, points[i].y);
    }
  }
 
 
  void setInitialAngle(char chr) {
    float randomFloat = random(1);
    switch(chr) {
      case '0':
        initialAngle = -0.01*PI;
        break;
      case '1':
        if (randomFloat<0.5) initialAngle = 0.115*PI;
        else initialAngle = -0.1*PI;
        break;
      case '2':
        if (randomFloat<0.5) initialAngle = 0.14*PI;
        else initialAngle = -0.17*PI;
        break;
      case '3':
        initialAngle = -0.15*PI;
        break;
      case '4':
        initialAngle = 0.135*PI;
        break;
      case '5':
        initialAngle = -0.1*PI;
        break;
      case '6':
        //initialAngle = 0.14*PI;
        initialAngle = 0.04*PI;
        break;
      case '7':
        initialAngle = -0.04*PI;
        break;
      case '8':
        initialAngle = -0.03*PI;
        break;
      case '9':
        initialAngle = -0.095*PI;
        break;
 
 
    }
  }
 
 
  boolean bodyCreated(){
    return m_bodyCreated;
  }
 
  void draw(PGraphics applet){
    preDraw(applet);
    m_shape.draw(applet);
    postDraw(applet);
  }
}

chewie-clock


I chose to make a clock that looked like nothing more than an ordinary video. The way it works is the bottle of water is drank over the course of an hour, meaning that the level of the water gives you an idea for how far into the hour it is (the height of the water resets to the bottom of the label). The hours are indicated by the street lights. Reading from the closest stop light as the most significant digit, you can read the hour in binary from the color of each consecutive light. Both of these functions can be manipulated by the user by typing 1-4 to change the color of each respective light, and 5 to take a sip.  The most challenging part of this project was figuring out a way to organize and execute longer animations from a sequence of images. The gif above shows how each layer can be isolated and animated independently . I like that this clock doesn't reveal itself easily. I tried to present it as a single shot, as if it were a live video recording of real events. And although these events seem to be happening over time, time never passes in this scene.

I made these car assets because I originally planned on including them as a component of the clock. I may spend more time in the future to get this working.

These gifs show how the empty road was constructed from pieces of a long video, over time.

dinkolas-clock

This project was very frustrating for me. I had an initial idea of a monster that would grow parts every second, and by the end of each day would be a total mess of limbs. However, I couldn't figure out a good/efficient way to do that, so I simplified my idea to faces. I also wanted there to be a calendar mode in which you could see characters from previous days/hours/minutes. I spent a long time trying to get the calendar to work, but for some reason it kept breaking in different ways.

Anyway, the final result that I do have is not very satisfying to me. It doesn't really have any personality, and each minute is hardly unique. Maybe next time things will turn out better.

var seed;
var ear;
var eye;
var eyeIris;
var mouthLip;
var mouthJaw;
var mouthTongue;
var noseFront;
var noseBack;
var t = new Date();
var currentMin = -1;
var face = [];
function setup() {
  createCanvas(600, 360);
  colorMode(HSB, 1);
  ear = loadImage("https://i.imgur.com/il1frFT.png")
  eye = loadImage("https://i.imgur.com/8yvvzFW.png")
  eyeIris = loadImage("https://i.imgur.com/3ZpiPHQ.png")
  mouthLip = loadImage("https://i.imgur.com/YAltCcA.png")
  mouthJaw = loadImage("https://i.imgur.com/UZXYDA5.png")
  noseFront = loadImage("https://i.imgur.com/5QIAcvW.png")
  noseBack = loadImage("https://i.imgur.com/khomfzm.png")
}
function realRand()
{
 	for (var i = 0; i < seed; i++)
  {
   	random(); 
  }
}
 
function draw() {
 
    t = new Date();
    if (currentMin != t.getMinutes())
    {
      bgCol = color(random(0,1),random(0.3,0.8),random(0.7,1));
      newFace(seed);
      currentMin = t.getMinutes();
    }
    background(0,0,1);
 
    for (var s = 0; s <= t.getSeconds(); s++)
    {
      drawFeature(face[s]); 
    }
    blendMode(MULTIPLY);
    background(bgCol);
    blendMode(BLEND);
}
 
function newFace() {
  var order = randomSixtyList();
  print(order);
 
  for (var i = 0; i < 60; i++)
  {
    var X = order[i] % 10;
    var Y = floor(order[i] / 10);
    //face format: [x,y,featureType,phaseShift,rotation, scale]
    face.push([X*60,Y*60,random(0,1),random(0,1),random(-PI,PI), random(0.5,1.5)]);
  }
}
 
function drawFeature(feature)
{
  push();
  translate(feature[0]+30,feature[1]+30);
  scale(feature[5]);
  if (feature[2] < 1/4){drawMouth(feature);}
  else if (feature[2] < 2/4){drawNose(feature);}
  else if (feature[2] < 3/4){drawEye(feature);}
  else {drawEar(feature);}                   
  pop();
}
 
function drawEar(feat)
{
  phase = feat[3]
  rot = feat[4]
  xOff = mouseX - feat[0];
  yOff = mouseY - feat[1];
  push()
  rotate(rot);
  scale(1 + 0.02*sin((millis()/1000+phase)*2*PI), 1)
  image(ear, -20, -30, 60, 60);
  pop()
}
 
function drawEye(feat)
{
  phase = feat[3]
  rot = feat[4]
  xOff = mouseX - feat[0] - 30;
  yOff = mouseY - feat[1] - 30;
 
  push()
  translate(xOff/50,yOff/50)
  rotate(rot);
  image(eyeIris, -30, -25, 60, 60);
  pop()
 
  push()
  rotate(rot);
  image(eye, -30, -30, 60, 60);
  pop()
}
 
function drawNose(feat)
{
  phase = feat[3]
  rot = feat[4]
  xOff = mouseX - feat[0];
  yOff = mouseY - feat[1];
 
  push()
  translate(0,0);
  rotate(rot);
  image(noseBack, -30, -30, 60, 60);
  pop()
 
  push()
  translate(xOff/100,yOff/100)
  rotate(rot);
  scale(1.2);
  image(noseFront, -30, -31, 60, 60);
  pop()
}
 
function drawMouth(feat)
{
  phase = feat[3]
  rot = feat[4]
  xOff = mouseX - feat[0];
  yOff = mouseY - feat[1];
 
  push()
  translate(-xOff/100,-yOff/100)
  rotate(rot);
  translate(0,0 * (1 + sin((millis()/1000+phase)*2*PI)))
  scale(0.8)
  image(mouthJaw, -30, -20, 60, 60);
  pop()
 
  push()
  translate(xOff/300,yOff/300)
  rotate(rot);
  image(mouthLip, -30, -30, 60, 60);
  pop()
}
 
function randomSixtyList()
{
  var out = [];
  for (var i = 0; i < 60; i++)
  {
    out.push(i);
  }
  for (var j = 0; j < 60; j++)
  {
   	var swap = floor(random(0,60));
    var J = out[j];
    out[j] = out[swap];
    out[swap] = J;
  }
  return out;
}

 

Spoon-Clock

Instead of creating a clock that displayed time in a useful manner, I decided to explore the experience of time. The resulting product is a giant eye that looks at the audience all day. It wakes up at 6AM every morning, blinks on a random cycle (the blinking motion is powered by an adjustable-center elliptic window function), and it falls asleep by 11PM every night. To reflect the light changing throughout the day, the creature's skin gets darker at night and lighter during the day. In addition while the creature sleeps, a monster wakes up to haunt you in the night. Rather than a green eye, the monster has a bright yellow eye. Rather than being awake from 6 to 11, the monster is awake from 1AM to 4AM.

I am happy with the way this piece turned out, as there are a few details I paid close attention to that came out nicely: the motion of the blinking, the colors, the fact that the creature slowly wakes up and falls asleep. There are a few details, though, that I am unhappy with or torn about. The pupil of the monster's eye could have been more feline rather than owl-like, but I couldn't quite make up my mind which to go with. Also, when the creature and the monster blink, the rounded stripes are not a constant width the whole way through. I was unable to figure out the geometry necessary to get the curves to line up perfectly, and this bugs me.

var STRIPE_WIDTH = 20;
var frameCounter;
var dayTime;
var midnight;
var lightness;
var human;
var monster;
 
 
function setup() {
	createCanvas(740, 740); 
	frameCounter = 0.0;
}
 
function draw() {
	checkTime();
	createStripes(color(255 * lightness, 100 * lightness, 100 * lightness));
	if(human) {
		createEye(width / 2, height / 2 + 10, 300, 160, color(0), 
							color(255 * lightness, 100 * lightness, 100 * lightness), 
							color(5, 150, 90));
	} else if(monster) {
		createMonsterEye(width / 2, height / 2 + 10, 300, 160, color(0), 
							color(255 * lightness, 100 * lightness, 100 * lightness), 
							color(255, 198, 0));
	}
}
 
function createStripes(c) {
	strokeWeight(STRIPE_WIDTH);
	for(var i = STRIPE_WIDTH / 2; i < height; i += STRIPE_WIDTH) {
		if((i - 10) % 40 == 0) stroke(0);
		else stroke(c);
		line(0, i, width, i);
	}
}
 
function createEye(x, y , eyeWidth, eyeHeight, stripe1, stripe2, iris) {
	noStroke();
	fill(255);
	ellipse(x, y, eyeWidth, eyeHeight);
	fill(iris);
	ellipse(x, y, eyeHeight, eyeHeight);
	fill(0);
	ellipse(x, y, eyeHeight / 2, eyeHeight / 2);
 
	noFill();
	var blinkNum = blink();
	for(var i = max((height - y) / 20, y / 20) - 1; i >= 0; i--) {
		i % 2 == 1 ? stroke(stripe2) : stroke(stripe1);
		bezier(x - eyeWidth / 2 - 6 * i + 5, y - 10 - i * 20 - 10, 
					 (x - eyeWidth / 2) + blinkNum * (eyeWidth / 7.5) + 5, 
					 y - 10 - blinkNum * dayTime * eyeHeight / 1.6 - i * 20 - 10,  
					 (x + eyeWidth / 2) - blinkNum * (eyeWidth / 7.5) - 5, 
					 y - 10 - blinkNum * dayTime * eyeHeight / 1.6 - i * 20 - 10,
					 x + eyeWidth / 2 + 6 * i - 5, y - 10 - i * 20 - 10);
 
		i % 2 == 1 ? stroke(stripe1) : stroke(stripe2);
		bezier(x - eyeWidth / 2 - 6 * i + 5, y + 10 + i * 20 + 10, 
					 (x - eyeWidth / 2) + blinkNum * (eyeWidth / 7.5) + 5, 
					 y + 10 + blinkNum * dayTime * eyeHeight / 1.6 + i * 20 + 10,  
					 (x + eyeWidth / 2) - blinkNum * (eyeWidth / 7.5) - 5, 
					 y + 10 + blinkNum * dayTime * eyeHeight / 1.6 + i * 20 + 10,
					 x + eyeWidth / 2 + 6 * i - 5, y + 10 + i * 20 + 10);
 
	}
	for(var i = max((height - y) / 20, y / 20) - 1; i >= 0; i--) {
		i % 2 == 1 ? stroke(stripe2) : stroke(stripe1);
		bezier(x - eyeWidth / 2 - 6 * i, y - 10 - i * 20, 
					 (x - eyeWidth / 2) + blinkNum * (eyeWidth / 7.5), 
					 y - 10 - blinkNum * dayTime * eyeHeight / 1.6 - i * 20,  
					 (x + eyeWidth / 2) - blinkNum * (eyeWidth / 7.5), 
					 y - 10 - blinkNum * dayTime * eyeHeight / 1.6 - i * 20,
					 x + eyeWidth / 2 + 6 * i, y - 10 - i * 20);
 
		i % 2 == 1 ? stroke(stripe1) : stroke(stripe2);
		bezier(x - eyeWidth / 2 - 6 * i, y + 10 + i * 20, 
					 (x - eyeWidth / 2) + blinkNum * (eyeWidth / 7.5), 
					 y + 10 + blinkNum * dayTime * eyeHeight / 1.6 + i * 20,  
					 (x + eyeWidth / 2) - blinkNum * (eyeWidth / 7.5), 
					 y + 10 + blinkNum * dayTime * eyeHeight / 1.6 + i * 20,
					 x + eyeWidth / 2 + 6 * i, y + 10 + i * 20);
	}
}
 
function createMonsterEye(x, y , eyeWidth, eyeHeight, stripe1, stripe2, iris) {
	noStroke();
	fill(iris);
	ellipse(x, y, eyeWidth, eyeHeight);
	fill(iris);
	ellipse(x, y, eyeHeight, eyeHeight);
	fill(0);
	ellipse(x, y, eyeHeight / 2, eyeHeight / 2);
 
	noFill();
	var blinkNum = blink();
	for(var i = max((height - y) / 20, y / 20) - 1; i >= 0; i--) {
		i % 2 == 1 ? stroke(stripe2) : stroke(stripe1);
		bezier(x - eyeWidth / 2 - 6 * i + 5, y - 10 - i * 20 - 10, 
					 (x - eyeWidth / 2) + blinkNum * (eyeWidth / 7.5) + 5, 
					 y - 10 - blinkNum * midnight* eyeHeight / 1.6 - i * 20 - 10,  
					 (x + eyeWidth / 2) - blinkNum * (eyeWidth / 7.5) - 5, 
					 y - 10 - blinkNum * midnight * eyeHeight / 1.6 - i * 20 - 10,
					 x + eyeWidth / 2 + 6 * i - 5, y - 10 - i * 20 - 10);
 
		i % 2 == 1 ? stroke(stripe1) : stroke(stripe2);
		bezier(x - eyeWidth / 2 - 6 * i + 5, y + 10 + i * 20 + 10, 
					 (x - eyeWidth / 2) + blinkNum * (eyeWidth / 7.5) + 5, 
					 y + 10 + blinkNum * midnight * eyeHeight / 1.6 + i * 20 + 10,  
					 (x + eyeWidth / 2) - blinkNum * (eyeWidth / 7.5) - 5,
					 y + 10 + blinkNum * midnight * eyeHeight / 1.6 + i * 20 + 10,
					 x + eyeWidth / 2 + 6 * i - 5, y + 10 + i * 20 + 10);
 
	}
	for(var i = max((height - y) / 20, y / 20) - 1; i >= 0; i--) {
		i % 2 == 1 ? stroke(stripe2) : stroke(stripe1);
		bezier(x - eyeWidth / 2 - 6 * i, y - 10 - i * 20, 
					 (x - eyeWidth / 2) + blinkNum * (eyeWidth / 7.5), 
					 y - 10 - blinkNum * midnight * eyeHeight / 1.6 - i * 20,  
					 (x + eyeWidth / 2) - blinkNum * (eyeWidth / 7.5), 
					 y - 10 - blinkNum * midnight * eyeHeight / 1.6 - i * 20,
					 x + eyeWidth / 2 + 6 * i, y - 10 - i * 20);
 
		i % 2 == 1 ? stroke(stripe1) : stroke(stripe2);
		bezier(x - eyeWidth / 2 - 6 * i, y + 10 + i * 20, 
					 (x - eyeWidth / 2) + blinkNum * (eyeWidth / 7.5), 
					 y + 10 + blinkNum * midnight * eyeHeight / 1.6 + i * 20,  
					 (x + eyeWidth / 2) - blinkNum * (eyeWidth / 7.5), 
					 y + 10 + blinkNum * midnight * eyeHeight / 1.6 + i * 20,
					 x + eyeWidth / 2 + 6 * i, y + 10 + i * 20);
	}
}
 
function blink() {
	//increment frameCounter
	if(frameCounter >= 0.98) {
		frameCounter = 0.0;	
	} else if((frameCounter == 0.0 && random(1000) < 5) || 
						(frameCounter > 0.0 && frameCounter < 1.0)) {
		frameCounter += 0.03;
	} else {
		frameCounter = 0.0;
	}
	console.log(frameCounter);
	return 1 - function_AdjustableCenterEllipticWindow(frameCounter, 0.4);
}
 
function checkTime() {
	var h = hour();
	var m = minute();
	var s = second();
	if(h < 22 && h > 5) {
		dayTime = 1.0;
		human = true;
	} else if(h >= 23 || h < 5 || (h == 5 && m < 30)) {
		dayTime = 0.0;
		human = false;
	} else if(h == 22) {
		dayTime = 1.0 - (m * 60 + s) / 3600.0;
		human = true;
	} else {
		dayTime = ((m - 30) * 60 + s )/ 1800.0;
		human = true;
	}
 
	if(h > 6 && h < 19) {
		lightness = 1.0;	
	} else if(h > 20 || h < 4) {
		lightness = 0.1;
	} else if(h >= 19 && h <= 20) {
		lightness = map(((h - 19) * 3600 + m * 60 + s) / 3600, 0, 2, 0.1, 1);
	} else {
		lightness = map(((h - 4) * 3600 + m * 60 + s) / 3600, 0, 3, 0.1, 1);
	}
 
	if(h > 0 && h < 4) {
		midnight = 1.0;
		monster = true;
	} else if(h == 12) {
		midnight = (m * 60 + s) / 3600.0;
		monster = true;
	} else if(h == 4) {
		midnight = 1.0 - (m * 60 + s) / 3600.0;
		monster = true;
	} else {
		midnight = 0.0;
		monster = false;
	}
}
 
//borrowed from Golan Levin: https://github.com/golanlevin/Pattern_Master
function function_AdjustableCenterEllipticWindow(x, a){
  //functionName = "Adjustable-Center Elliptic Window";
 
  var min_param_a = 0.0 + Number.EPSILON;
  var max_param_a = 1.0 - Number.EPSILON;
  a = constrain(a, min_param_a, max_param_a);
 
  var y = 0;
 
  if(x<=a) {
    y = (1.0/a) * sqrt(sq(a) - sq(x-a));
  } 
  else {
    y = (1.0/(1-a)) * sqrt(sq(1.0-a) - sq(x-a));
  }
  return y;
}

lass-clock

   

For this project, I wanted to make a tidal clock using 3js and shaders. The water rises and cycles through high and low tide twice every ~25 hours. Originally, my design involved a floating island with some water pooled in the middle, but as I worked on the project it I realized that floating islands didn't really make sense and instead used the shape of a tidal pool.

One feature of the clock is that the sky changes during day, night, and sunset. I chose to have this feature because since my shapes were low poly, I wanted to be able to have a wider variety of color palettes.    

       

One of the things that I would have liked to include in this project is using location services and to find the actual tide for the user's location. Right now, I'm basing the tide off of the most recent high tide on Virginia Beach, which will work temporarily but probably require calibration in the future since the length of tidal days is approximate. The same thing goes for sunrise and sunset times, since right now they are shown at a fixed time every day.

<script id="waterVertex" type="x-shader/x-vertex">
    uniform float u_time; 
    uniform float u_height; 
    varying vec3 v_position; 
    varying vec3 v_normal; 
 
    float random(vec2 co){
        return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
    }
 
    void main() {
        vec3 newPosition = position;    
        newPosition.z = cos(u_time / 3.0) / 10.0; 
        newPosition.z += u_height + 0.1; 
        newPosition.z += random(position.xy) / 15.0; 
        v_position = newPosition; 
        gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
    }
</script>
<script id="waterFragment" type="x-shader/x-fragment">
    varying vec3 v_position; 
    uniform float u_radius; 
    uniform vec3 u_camera; 
    uniform float u_hour; 
 
    void main() {
        float fog = distance(u_camera, v_position) / 20.0; 
        float day = clamp(6.0 - abs(12.0 - u_hour), 0.0, 1.0); 
        vec3 darkBlue = vec3(fog / 4.0, 0.25, 0.5); 
        vec3 lightBlue = vec3(fog, 0.85, 0.8); 
        gl_FragColor =  vec4(day * lightBlue + (1.0 - day) * darkBlue, 0.8 - fog ); 
    }
</script>
<script id="sandVertex" type="x-shader/x-vertex">
    varying vec2 vUV;
    varying vec3 v_normal; 
    varying vec3 v_position; 
 
    void main() {  
        vUV = uv;
        vec4 pos = vec4(position, 1.0);
        gl_Position = projectionMatrix * modelViewMatrix * pos;
        v_normal = normalize(normal);
        v_position = position; 
    }
</script>
 
<script id="sandFragment" type="x-shader/x-fragment">
    varying vec3 v_normal; 
    varying vec3 v_position; 
    uniform vec3 u_camera; 
 
    void main() {
        vec3 lightSource = normalize(vec3(0.0, 1.0, 3.0)); 
        float dprod = max(dot(v_normal, lightSource), 0.0); 
        vec3 highlightColor = vec3( 0.7, 0.7, 0.2);
        vec3 shadowColor = vec3(0.3, 0.3, 0.6); 
        float fog = pow(distance(vec3(0.0), v_position) , 2.0) / 30.0; 
        gl_FragColor = vec4( shadowColor + highlightColor  * dprod * 0.4, 0.9 - fog);
    }
</script>
 
<script id="skyVertex" type="x-shader/x-fragment">  
    varying vec2 vUV;
    varying float v_z; 
 
    void main() {  
        vUV = uv;
        vec4 pos = vec4(position, 1.0);
        gl_Position = projectionMatrix * modelViewMatrix * pos;
        v_z = normalize(position).z; 
    }
</script>
 
<script id="skyFragment" type="x-shader/x-fragment">  
    varying float v_z; 
    uniform float u_hour; 
 
    void main() {  
        vec3 dayColor = vec3(0.4 - v_z, 0.7 - v_z, 0.80);
        vec3 sunsetColor = vec3(0.5 + v_z , 0.2 + v_z, 0.3);
        vec3 nightColor = vec3(0.05 - v_z / 10.0, 0.05 - v_z / 5.0, 0.20);
 
        vec3 dtn = vec3(0.0);
        dtn.x = clamp(6.0 - abs(12.0 - u_hour), 0.0, 1.0); 
        dtn.z = 1.0 - dtn.x; 
        dtn.y = (clamp(sin(u_hour * 3.14 / 12.0), 0.5, 1.0) - 0.5) * 2.0; 
        dtn = normalize(dtn); 
 
        gl_FragColor = vec4(dtn.x * dayColor + dtn.y * sunsetColor + dtn.z * nightColor, 1.0); 
    }
</script>  
 
 
<script>
 
    //https://thebookofshaders.com/04/
    var container;
    var camera, scene, renderer, controls;
    var waterUniforms, skyUniforms; 
    var water, tidehand, sand, clockface; 
    var angle, date, height; 
    var pastHighTide = new Date("September 20, 2018 5:12:00")
 
    init();
    animate();
 
    function init() {
        container = document.getElementById( "container" );
 
        camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
        camera.position.z = 6;
        controls = new THREE.OrbitControls( camera );
        controls.maxDistance = 7; 
        controls.minDistance = 1; 
        controls.enableDamping = true; 
        controls.dampingFactor = 0.2;
        controls.maxAzimuthAngle = Math.PI / 4; 
        controls.minAzimuthAngle = Math.PI / -4; 
        controls.minPolarAngle = Math.PI / 4; 
        controls.maxPolarAngle = Math.PI * 3 / 4; 
 
        scene = new THREE.Scene();
 
        date = new Date(); 
        height = Math.cos(-1 * angle) / 7.0; 
 
        var geometry = new THREE.PlaneGeometry(50, 50, 100, 100); 
 
 
        waterUniforms = {
            u_time: { type: "f", value: 1.0 },
            u_height: { type: "f", value: height},
            u_camera: {type:"v3", value: camera.position}, 
            u_hour: {type: "f", value: date.getHours() + date.getMinutes() / 60}
        };
 
        var material = new THREE.ShaderMaterial( {
            uniforms: waterUniforms,
            vertexShader: document.getElementById("waterVertex").textContent,
            fragmentShader: document.getElementById("waterFragment").textContent, 
            transparent: true, 
            depthWrite: false, 
            side: THREE.DoubleSide
 
        } );
        water = new THREE.Mesh( geometry, material );
        scene.add( water );
 
        //sand geometry made in blender
        var loader = new THREE.JSONLoader();
        loader.load("tallsand.json", 
            function(geometry, materials){
                var sandUniforms = ({
                    u_camera: {type:"v3", value: camera.position}, 
                    u_hour: {type: "f", value: date.getHours() + date.getMinutes() / 60}
                }); 
                material = new THREE.ShaderMaterial( {
                    uniforms: sandUniforms, 
                    vertexShader: document.getElementById("sandVertex").textContent,
                    fragmentShader: document.getElementById("sandFragment").textContent, 
                    transparent: true, 
                } );
                var sandy = new THREE.Mesh(geometry, material); 
                sandy.rotation.x += Math.PI / 2; 
                sandy.scale.set(2, 2, 2); 
                sandy.position.z = -0.001; 
                scene.add(sandy); 
            }, 
            function(xhr){console.log("loaded json")},
            function(err){console.log("error loading json")}
        );
 
        geometry = new THREE.PlaneGeometry( .1, 2);
        material = new THREE.MeshBasicMaterial( { color: 0xffffff, side: THREE.DoubleSide, transparent:true} );
        stick = new THREE.Mesh( geometry, material );
        stick.position.y += .9; 
        tidehand = new THREE.Group(); 
        tidehand.add(stick); 
        tidehand.rotation.z = angle; 
        tidehand.position.z += 1.01; 
        scene.add( tidehand );
 
        var texture = new THREE.TextureLoader().load("clockface.png"); 
        geometry = new THREE.PlaneGeometry(6, 6); 
        material = new THREE.MeshBasicMaterial({map:texture, transparent: true, depthWrite: false}); 
        clockface = new THREE.Mesh(geometry, material); 
        clockface.position.z += 1;
        scene.add(clockface); 
 
        //skydome code from Ian Webster http://www.ianww.com/blog/2014/02/17/making-a-skydome-in-three-dot-js/
        //we are inside of a sphere! 
        geometry = new THREE.SphereGeometry(300, 60, 40);  
        skyUniforms = {
            u_hour: {type: "f", value: date.getHours() + date.getMinutes() / 60}
        }
        material = new THREE.ShaderMaterial( {  
            uniforms: skyUniforms,
            vertexShader:   document.getElementById("skyVertex").textContent,
            fragmentShader: document.getElementById("skyFragment").textContent,
            side: THREE.DoubleSide
        });
        skyBox = new THREE.Mesh(geometry, material);  
        skyBox.eulerOrder = "XZY";  
        skyBox.renderDepth = 1000.0;  
        scene.add(skyBox);  
 
        renderer = new THREE.WebGLRenderer();
        renderer.setPixelRatio( window.devicePixelRatio );
        container.appendChild( renderer.domElement );
        onWindowResize();
        window.addEventListener( "resize", onWindowResize, false );
    }
 
    function onWindowResize(event) {
        renderer.setSize( window.innerWidth, window.innerHeight );
    }
 
    function animate() {
        updateTide(); 
        waterUniforms.u_time.value += 0.05;
 
        controls.update(); 
        requestAnimationFrame( animate );
        render();
    }
 
    function updateTide() {
        date = new Date(); 
        //date.setMinutes(date.getMinutes() + 1); //artificial fast forward
        document.getElementById("info").innerHTML = date.toLocaleString(); 
        var diff = (date - pastHighTide) / 60000; 
        diff = diff % 745; 
        angle = -1 * Math.PI * 2 * diff / 745;
        height = Math.cos(-1 * angle) / 7.0; 
        waterUniforms.u_height.value = height; 
        skyUniforms.u_hour.value = date.getHours() + date.getMinutes() / 60; 
        waterUniforms.u_hour.value = date.getHours() + date.getMinutes() / 60; 
 
        tidehand.rotation.z = angle; 
    }
 
    function render() {
        renderer.render( scene, camera );
    }
 
    document.addEventListener("keydown", function(e){
        switch(e.keyCode){
            case 32: 
                controls.reset(); 
                break; 
        } 
    });
</script>

 

nerual-Clock

 

Between 1am and 8am, BERT-O-CLOCK sleeps, but he can be woken to give you the time. Between 8am and 10am, he sleeps, and he won't wake up. Otherwise, he's awake and keeping time.

And at a very particular time, BERT-O-CLOCK likes to have some fun.

I was really interested in the notion of using hands and fingers to count time, like how small children learn math. This lead me on this path of a childish aesthetic and manner of interaction with the piece. I thought it would be interesting to have this snarky little entity that "keeps" the time. So it "keeps time" by keeping track of what time it is, but sometimes it sleeps, and keeps the time from you that way, and sometimes it sleeps and refuses to wake up. I wanted to capture this sense of time being out of your control, something that seems to act on its own accord. And I think that although this is rather apparent to children, it's something we tend to forget as we grow older.

I think I made the right choice following the whim that lead down this hole. It has some liveliness to it, and I think the tick tock animation at the top was a good touch. I personally think it's fun to interact with, especially if you had this as a small app on your mobile device. It doesn't involve much more than basic javascript functions, and my style ended up being guided by what I could figure out in p5.js. The color scheme is an attempt at something, but the success of that attempt is questionable. The binary representation with the fingers was done more out of practicality than purpose, but it contributes to the frustration I intended the viewer to have with the small creature. Whether that frustration translates to anything, I can't say.

Future Work:

  • make Bert into a mobile app
  • animate his hands going up and down
  • animate his mouth

Sketches:

Code:

var r = 175;
var cx,cy;
var e = 60;
var bop = 3;
var state; //"sleep","snark"
var nappyTime = [1,8];
var snarkTime = [8,9];
var funTime = [2,34,60];
var bodyColor = '#f7786b';
var clicked;
var snarked;
 
var H, M, S, mil;
 
function setup() {
  createCanvas(400,600);
  cx = width/2;
  cy = height;
  console.log("setup complete");
  state = "sleep";
  colorBody();
  clicked = false;
  var snarked = false;
}
 
function draw() {
  H = hour();
  M = minute();
  S = second();
  mil = millis();
  background('#00f5eb');
  //debug();
  if (H==funTime[0] &amp;&amp; M==funTime[1])
    party();
  updateState();
  drawBert();
  drawText();
  if (snarked)
    snark();
}
 
function debug(){
  text((S &gt;&gt;&gt; 0).toString(2),width/2, 100);
  text(H + ":" + M + ":" + S ,width/2, 120);
  text(bin(H,2) ,width/2, 140);
}
 
function drawText(){
  var def= "This is a \n BERT-O1-CLOCK.\nHe keeps the time."
  var sleep = "Bert is asleep right now. \nI wonder when he'll wake up...\n Maybe you should poke him?";
  var wake = "He's a 1's and 0's\n kind of guy. Have fun.";
  textFont("Courier New");
  stroke('#fdff5c'); fill('#fdff5c');
  //colorBody();
  textSize(30);
  textAlign(CENTER);
  text(def, width/2, height/5);
 
  stroke('#0'); fill('#0');
  textSize(20);
  if (state=="sleep" || (state=="snark" &amp;&amp; !snarked)){
    text(sleep, width/2, height/2);
  }
  else{
    text(wake, width/2, height/2 - 50);
  }
 
  if(S%2==1)
    text("tick", 60,60);
  else
    text("tock", width-60, 60);
 
}
 
function colorBody(){
  fill('#f7786b');
    //fill('#ff5147');
  //fill(255,153,153);
}
 
function getTime(){
  return [hour(), minute(), second()];
}
 
function snark(){
  stroke(255); fill(255);
  textSize(20);
  var snark = "BERT-O1-CLOCK wakes up \n when he damn well pleases."
  text(snark, width/2,height/2);
  snarked = true;
}
 
 
function updateState(){
  var t = getTime();
  var yay = false;
  if (nappyTime[0]&lt;=t[0] &amp;&amp; t[0]&lt;=nappyTime[1]){
    if(!clicked)
      state = "sleep";
      snarked = false;
  }
  else if (snarkTime[0]&lt;=t[0] &amp;&amp; t[0]&lt;=snarkTime[1]){
    state = "snark";
  }
  else{
    state = "wake";
    snarked = false;
  }
  console.log(state);
}
 
function mousePressed(){
  console.log("click");
  if (state=="wake")
    state="sleep";
  else if (state=="snark"){
    if (!snarked)
      snark();
    else
      snarked = false;
  }
  else
    state="wake";
  clicked = true;
}
 
function party(){
  push();
  background('#f000eb');
  textSize(45);
  textStyle(BOLD);
  colorBody();
  text("IT IS THE HOUR",width/2,60);
  stroke(255); fill(255);
  text("IT IS THE HOUR",width/2+5,60+5);
  rotate(PI/4);
  translate(width/2,-height/2);
  pop();
}
 
 
function calcBop(){
  var t = map(mil, 0, 999, 0, 1);
  var yPos = map(cos(t * TWO_PI), -1, 1, 0, bop);
  translate(0,yPos);
}
 
function drawBert(){
  if (state=="wake")
    drawHands();
  push();
    calcBop();
    drawBody();
  pop();
}
 
function drawBody(){
  //draw boi
  noStroke();
  colorBody();
  ellipse(cx, cy, 2*r, 2*r);
  //draw eyes
  drawEyes();
  drawMouth();
  //nose
  stroke(255);
  strokeWeight(5);
  colorBody();
  var nose = 100;
  arc(cx,cy-r/2+10,nose/2,nose,0,PI);
}
 
function drawEyes(){
  if (state=="wake"){
    noStroke();
    fill(255);
    ellipse(cx-r/2, cy-r/2, e, e/2);
    ellipse(cx+r/2, cy-r/2, e, e/2);
    fill(0);
    ellipse(cx-r/2, cy-r/2, e/2, e/2);
    ellipse(cx+r/2, cy-r/2, e/2, e/2);
  }
  else{
    stroke(255);
    strokeWeight(5);
    noFill();
    arc(cx-r/2, cy-r/2, e, e/2, 0, PI);
    arc(cx+r/2, cy-r/2, e, e/2, 0, PI);
  }
}
 
 
//add to this
function drawMouth(){
  //mouth
  stroke(255);
  strokeWeight(5);
  fill(255);
  if (state=="wake")
    ellipse(cx,cy-r/4+10, r, r/10);
  else
    line(cx-r/4,cy-r/4,cx+r/4,cy-r/4);
 
}
 
function drawHands(){
  rectMode(CENTER);
  noStroke();
      colorBody();
  //left
  push();
  translate(cx-r/3,cy);
  drawHand(1,2); //hr1
  translate(-r/3-10, 0);
  drawHand(1,3); //hr2
  pop();
  //right
  push();
  translate(cx+r/3,cy);
  drawHand(-1, 1);  //min2
  translate(r/3+10,0);
  drawHand(-1, 0); //min1
  pop();
}
 
function bin(p, i){
  var b = (p &gt;&gt;&gt; 0).toString(2);
  if (i &gt;= b.length)
    return '0';
  else
    return b.charAt(b.length-i-1);
}
 
var cap = 10;
var w = 50;
var fw = 10; //finger width
var fcap = 5;
var fl = 60;
var arml = r*3
function drawHand(side, place=5){
  //place=0 - min1
  //place=1 - min2
  //place=2 - hr
  colorBody();
  //arm
  rect(0, 0, w, arml, cap);
  switch (place) {
    case 0:
      drawFinger(3, bin(M,3)==1);
      drawFinger(2, bin(M,2)==1);
      drawFinger(1, bin(M,1)==1);
      drawFinger(0, bin(M,0)==1);
      break;
    case 1:
      drawFinger(3, bin(M,7)==1);
      drawFinger(2, bin(M,6)==1);
      drawFinger(1, bin(M,5)==1);
      drawFinger(0, bin(M,4)==1);
      break;
    case 2:
      drawFinger(3, bin(H,3)==1);
      drawFinger(2, bin(H,2)==1);
      drawFinger(1, bin(H,1)==1);
      drawFinger(0, bin(H,0)==1);
      break;
    case 3:
      drawFinger(3, bin(H,7)==1);
      drawFinger(2, bin(H,6)==1);
      drawFinger(1, bin(H,5)==1);
      drawFinger(0, bin(H,4)==1);
      break;
    case 4:
      drawFinger(3, bin(S,3)==1);
      drawFinger(2, bin(S,2)==1);
      drawFinger(1, bin(S,1)==1);
      drawFinger(0, bin(S,0)==1);
      break;
    default:
      drawFinger(3);
      drawFinger(2);
      drawFinger(1);
      drawFinger(0);
      break;
  }
  //thumb
  push();
  translate(side*w/2,fw-arml/2);
  rotate(-PI/2);
  rect(0,0, fw, fl/2, fcap);
  pop();
}
 
function drawFinger(n,up=true) {
  colorBody();
  noStroke();
  var yPos = -arml/2;
  if (!up) 
    yPos=yPos + 2*fl/5;
  switch(n){
    case 3:
      rect(-w/2+fw/2, yPos, fw, fl, fcap);
      break;
    case 2:
      rect(-w/4+fw/2, yPos, fw, fl, fcap);
      break;
    case 1:
      rect(w/4-fw/2, yPos, fw, fl, fcap);
      break;
    case 0:
      rect(w/2-fw/2, yPos, fw, fl, fcap);
      break;
    default:
      fill(0);
      elllipse(width/2,height/2,100,100);
  }
}

shuann-clock

I had a lot of trouble coming up with ideas, but I really liked one of the ancient time keeping tool that only tracked time to the hours. In addition I also wanted to crate a clock that can blends into the everyday routine that you shouldn't need to pay much attention to.  I was then inspired by "rainy mood (https://rainymood.com/)" which I listen to while working as the rain sound can keep me calm and concentrated. Thus I decided to make this clock that is all about that. It is designed so that you don't really need to pay much attention to the visuals. For every hour that passed by, there will be a thunder sound to remind you that it's now the next hour.

I am overall satisfied with the final product. I originally imagined this to be a full scale projection that blends into a wall to create the illusion of a rainy day. However, I had to scale it down due to 1). time constraints and 2). the style of computer graphics might not be the best to create that illusion(?).

Sketch:

var rain = [];
var offset = 30;
var s;
var m;
var h;
var preH=-1;
var sizeY = 10;
var sizeO = 9;
 
 
function preload() {
    rainSound = loadSound("rain.ogg");
    thunder = loadSound("thunder.ogg");
}
 
function setup() {
  createCanvas(500,500); 
  frameRate(30);
  rainSound.loop();
}
 
function draw() {
  drawBackground();
  s = second();
  m = minute();
  h = hour();
 
  if (h != preH){
    thunder.play();
    preH=h;
  }
 
  if (frameCount % 10 == 0){
    createNewDrop();
  }
 
  push();
  for (var elem=0; elem &lt; rain.length; elem++){
    rain[elem].move();
    if (rain[elem].bound() &amp;&amp; elem != rain.length-1){
      rain.splice(elem,1);
    }
    rain[elem].render();
  }
  pop();
 
  drawWall();
  drawCandle();
}
 
function drawCandle(){
  //plate
  push();
  translate(width*2/3-20, height*2/3-20);
  fill("lightpink");
  noStroke();
  rect(-80,0,160,7);
  ellipse(0,7,160,20);
  fill("pink");
  ellipse(0,0,160,20);
  pop();
 
  push();
  translate(width*2/3-20, height*2/3-20);
 
  noStroke();
  fill("offWhite");
  if(h &lt; 12){// candle length mapped to miuntes &amp;&amp; burn for 12h each
    //candle1
    var c1 = map(h*60+m, 0, 720, -60, -5);
    ellipse(-30,c1-3,50,10);
    rect(-55,c1-3,50,-c1);
    ellipse(-30,-3,50,10);//stationary
    stroke(0);
    strokeWeight(2);
    line(-30,c1-3,-30,c1-7);
    drawFire(-30,c1-7);
 
    //candle2
    noStroke();
    ellipse(25,-58,50,10);
    rect(0,-58,50,60);
    ellipse(25,2,50,10);//stationary
    stroke(0);
    strokeWeight(2);
    line(25,-58,25,-62);
 
 
  } else {
    ellipse(-30,-8,50,10);
    rect(-55,-8,50,5);
    ellipse(-30,-3,50,10);//stationary
    stroke(100);
    strokeWeight(2);
    line(-30,-8,-30,-12);
 
    var c2 = map(h%12*60+m, 0, 720, -60, -5);
    noStroke();
    ellipse(25,c2+2,50,10);
    rect(0,c2+2,50,-c2);
    ellipse(25,2,50,10);//stationary
    stroke(0);
    strokeWeight(2);
    line(25,c2+2,25,c2-2);
    drawFire(25,c2-2);
  }
  pop();
}
 
function drawFire(x,y){
  push();
  translate(x,y);
  noStroke();
  fill(225,243,94,100);
  if (s%2 == 0){
    sizeY += 0.5;
    sizeO += 0.1;
  } else{
    sizeY -= 0.5;
    sizeO -= 0.1;
  }
  ellipse(0,-4,8,sizeY);
  fill(231,122,19,200);
  ellipse(0,-3,7,sizeO);
  fill(241,60,47,240);
  ellipse(0,-2,4,7);
 
  pop();
}
 
function drawBackground(){
  background(150);
  var r0 = 0;
  var g0 = 76;
  var b0 = 153;
  var r1 = 70;
  var g1 = 130;
  var b1 = 180;
  for (var i=0; i &lt; 30; i++){
    noStroke();
    var r = map(i,0,30,r0,r1);
    var g = map(i,0,30,g0,g1);
    var b = map(i,0,30,b0,b1);
    fill(r,g,b);
    rect(0,i*10,width/2+offset*2-5,10);
  }
}
 
function drawWall(){
  push();
  fill(	251, 241, 224);
  noStroke();
  rect(width/2+offset*2,0,width/2+30,height);
  rect(0,height*2/3-38,width,height/2);
 
  push();
  // fill(245,232,202);
  fill(220,210,180);
  translate(width/2+offset*2,0);
  quad(0,0,20,0,20,299,0,290);
  pop();
 
  push();
  fill(255,250,250);
  translate(0,height*2/3-45);
  quad(0,0,width/2+75,0,width/2+80,10,0,10);
  pop();
 
  pop();
  drawFrame();
}
 
function drawFrame(){
  push();
  fill(92,53,33);
  noStroke();
  quad(width/2+offset*2-5,0,width/2+offset*2+15,0,
      width/2+offset*2+15,height*2/3-45,width/2+offset*2-5,height*2/3-60);//right
  fill(97,62,38);
  quad(0,height/2+23,width/2+offset*2-5,height/2+23,
      width/2+offset*2+16,height/2+39,0,height/2+39);//bottom
  pop();
 
  drawStand();
  drawFlower();
  drawBucket();
}
 
function drawBucket(){
  push();
  translate(50,height*0.8);
  strokeWeight(5);
  stroke(80);
  noFill();
  ellipse(0,0,160,20);
 
  push();
  strokeWeight(10);
  stroke(161,122,99);
  strokeJoin(ROUND);
  beginShape();
  curveVertex(60,-60);
  curveVertex(60,-60);
  curveVertex(70,-90);
  curveVertex(78,-100);
  curveVertex(90,-103);
  curveVertex(100,-100);
  curveVertex(104,-94);
  curveVertex(105,-90);
  curveVertex(105,-85);
  curveVertex(103,-85);
  endShape();
  //umbrella
  noStroke();
  scale(1.5);
  translate(-25,0);
  fill(119,191,225);
  triangle(40,-50,70,-40,0,200);
  fill(107,172,229);
  triangle(45,-50,75,-40,0,200);
  fill(115,185,247);
  triangle(50,-50,80,-40,0,200);
  fill(117,199,252);
  triangle(60,-48,80,-42,0,200);
  fill(115,185,247);
  triangle(70,-44,90,-35,0,200);
  pop();
 
  line(80,0,70,100);
  line(80,0,70,5);
  line(70,5,60,8);
  line(60,8,52,100);
  line(60,8,50,9);
  line(50,9,40,9.5);
  line(40,10,35,100);
  line(40,9.5,20,9.5);
  line(20,10,18,100);
  line(20,9.5,10,9.5);
  line(0,10,0,100);
  line(-20,10,-18,100);
  line(-40,9,-35,100);
  pop();
}
 
function drawStand(){
  push();
  translate(width/3-20,height/2+offset*2);
  noStroke();
  fill(216,200,225);
  quad(20,0,380,0,400,30,0,30);
  fill(216,191,216);
  rect(0,30,400,15);
  fill(201,186,209);
  rect(10,45,380,250);
 
  //drawer1
  fill(220,204,209); 
  quad(50,60,430,60,420,70,55,70);
  quad(55,130,430,130,430,135,50,135);
  fill(242,224,230);
  quad(50,60,55,70,55,130,50,135);
 
  //drawer2
  fill(220,204,209);
  quad(50,160,430,160,420,170,55,170);
  fill(242,224,230);
  quad(50,160,55,170,55,230,50,240);
  pop();
}
 
function drawFlower(){
  push();
  //draw stem
  translate(width/2+170,height/3+25);
	stroke(52,131,34);
	strokeWeight(4);
	line(-20,-100,55,128);
	line(5,-30,10,-50);
	line(10,-50,50,-90);
	line(10,-50,12,-90);
  line(23,30,60,-40);
	line(10,-9,-70,-60);
	line(-33,-35,-60,-20);
	line(-20,-100,-50,-120);
	line(-20,-100,-20,-130);
 
	//draw buds 
	noStroke();
	fill(220,182,225);
	star(10,-50, 10, 20,7);
	star(50,-90, 13, 25,7);
	star(12,-90, 13, 25,7);
	star(60,-40, 13, 25,7);
	star(-70,-60, 13, 25,7);
	star(-33,-35, 10, 20,7);
	star(-50,-120, 10, 20,7);
	star(-25,-80, 13, 25,7);
	star(-15,-115, 13, 25,7);
	star(0,-15, 13, 25,7);
	star(-60,-20, 10, 20,7);
 
	fill("offwhite");
	ellipse(10,-50,7);
	ellipse(50,-90,7);
	ellipse(12,-90,7);
	ellipse(60,-40,7);
	ellipse(-70,-60,7);
	ellipse(-33,-35,7);
	ellipse(-50,-120,7);
	ellipse(-25,-80,7);
	ellipse(-15,-115,7);
	ellipse(0,-15,7);
	ellipse(-60,-20,7);
	pop();
 
	//draw vessel
	push();
	fill(198,228,255,200);
	noStroke();
	rect(width/2+170,height/3+25,60,130);
	ellipse(width/2+200,height/3+155,60,10);
	pop();
}
 
function createNewDrop(){
  for (var i = 0; i &lt; 10; i++){
    var newDrop = new rainDrop(int(random(-20,width/2+offset*2)), int(random(-20,height/3)));
    rain.push(newDrop);
  }
}
 
function rainDrop(x, y){
  this.x = x;
  this.y = y;
  this.s = 1;
  this.pos = [[this.x, this.y]];
  this.velX = int(random(1,3));
  this.velY = 2;
 
  this.render = function(){
    stroke(204,229,255);
    if (this.pos.length == 1) {
      ellipse(this.x, this.y, this.s, this.s);
    } else {
      for (var i=1; i&lt;this.pos.length; i++){
        strokeWeight(this.s);
        line(this.pos[i-1][0], this.pos[i-1][1], this.pos[i][0], this.pos[i][1]);
      }
    }
  }
 
  this.move = function() {
    this.x += this.velX;
    this.y += this.velY;
    this.pos.push([this.x, this.y]);
 
    if (random() &lt; 0.8){ this.velX = int(random(2)); } else { this.velX = int(random(-2,3)); } this.checkLength(); } pop this.checkLength = function() { if (this.pos.length &gt; 15){
      this.pos.shift(0);
    }
  }
 
  this.bound = function() {
    if (this.pos[0][0] &gt; width/2+offset*2 || this.pos[0][1] &gt; height*2/3-38){
      return true;
    } else {
      return false;
    }
  }
}
 
//taken from:https://p5js.org/examples/form-star.html
function star(x, y, radius1, radius2, npoints) {
  var angle = TWO_PI / npoints;
  var halfAngle = angle/2.0;
  beginShape();
  for (var a = 0; a &lt; TWO_PI; a += angle) {
    var sx = x + cos(a) * radius2;
    var sy = y + sin(a) * radius2;
    vertex(sx, sy);
    sx = x + cos(a+halfAngle) * radius1;
    sy = y + sin(a+halfAngle) * radius1;
    vertex(sx, sy);
  }
  endShape(CLOSE);
}

 

casher-clock

 

For my clock, I made a graphic that increases the number of sides in the shape according to the hour, has an increasing number of rings according to the minute, and pulses according to the second. The color also changes according to the specific time. I really wished I had had more time to work on this because there were some cool effects that I didn't get the chance to code. I had a lot of other work this week so I actually couldn't find a time to truly start it until tonight; nevertheless, I worked hard, and as you can see my third GIF render is from right now -- 2:10am. Gotta love coding.

I was stuck for a while at the beginning on how to actually generate shapes with sides according to the hour, so that it changes without me having to draw 12 different shapes and call them individually using if statements. Golan told me about beginShape(), so that new function with a bit of trig helped me generate the hourly shapes. And of course, Char came along and aided with the pulsing. However, this code is 100% my own! There were no templates from Golan or chunks from the internet. I am proud of that. Learning is cool!

nannon-03-clock

 

This is a 24 hour clock that grows back into itself. I actually had a hard time coming up with a solid concept for this project, and it definitely impacted the work. I originally wanted to do a "clock" based on the days of the user's life, with multiple spheres representing each day--spheres would alter in size and color, with different layers representing amount of fulfillment in relationships, work, etc. I had the sphere code from the beginning, but it became hard to scrape data and/or create user input in processing, at which point I decided to scope down. I'm not totally satisfied with =the simplification of the representation of multiple days to just a single day with a sphere, since it falls to close to just a normal clock. However, I like the emphasis on the regrowth visual/symbolism in the piece. Definitely need to focus on scoping down and making sure I have the capabilities for my vision in the next project thank you for the real talk @golan!!

 

 

 

--Process --

playing around with adding spheres (layers) on key pressed (earlier idea).

import peasy.*;
 
PeasyCam cam;
 
int time = 6;
int time2;
 void keyPressed() {
   if (keyCode == UP) {
     time2 = time++%24;
     println(time2);
   }
   if (keyCode == DOWN) {
     time2 = time--%24;
   }
 }
 
 
PVector[][] globe;
int total = 72;
float[][] firsts = new float[total+1][3];
float[][] lasts =  new float[total+1][3];
 
 
 
void setup() {
  size(640, 640, P3D);
  cam = new PeasyCam(this, 500);
  globe = new PVector[total+1][total+1];
 
 
}
 
void draw() {
 
 
 
  //println(hour());
  background(255);
  pushMatrix();
  textSize(20);
 
  fill(100);
 
  text(str(time2)+":00", -30,300);
  popMatrix();
  noFill();
  stroke(240);
  lights();
  float smallRad = map(time2*3, 0, 72,0,200);
  sphere(smallRad);
 
  float r = 200;
  for (int i = 0; i &lt; total+1; i++) {
    float lat = map(i, 0, total, 0, PI);
    for (int j = 0; j &lt; total+1; j++) {
      float lon = map(j, 0, total, 0, TWO_PI);
      float x = r * sin(lat) * cos(lon);
      float y = r * sin(lat) * sin(lon);
      float z = r * cos(lat);
      globe[i][j] = new PVector(x, y, z);
    }
  }
 
  for (int i = 0; i &lt; total; i++) {
    beginShape(TRIANGLE_STRIP);
    int curHour= 72-(time2*72)/24;
    //println(curHour);
    for (int j = 0; j &lt; curHour; j++) {
 
      PVector v1 = globe[i][j];
      vertex(v1.x, v1.y, v1.z);
      PVector v2 = globe[i+1][j];
      vertex(v2.x, v2.y, v2.z);
      if (j==0) {
        firsts[i][0] = v1.x;
        firsts[i][1] = v1.y;
        firsts[i][2] = v1.z;
      }
      if (j==curHour-1) {
        lasts[i][0] = v2.x;
        lasts[i][1] = v2.y;
        lasts[i][2] = v2.z;
      }
 
    }
    endShape();
  }
 
  fill(100);
  beginShape();
  vertex(0,0,0);
  for (int i=0; i &lt; total; i++) {
    vertex(firsts[i][0], firsts[i][1], firsts[i][2]);
  }
   vertex(0,0,cos(72)*200);
  endShape();
 
  fill(100);
  beginShape();
  vertex(0,0,0);
 vertex(firsts[0][0], firsts[0][1], firsts[0][2]);
  for (int i=0; i &lt; total; i++) {
    vertex(lasts[i][0], lasts[i][1], lasts[i][2]);
  }
  //vertex(firsts[0][0], firsts[0][1], firsts[0][2]);
  vertex(0,0,cos(2*PI)*r);
  endShape();
 
 
}

rigatoni – clock

let measure = 10
let size = 150
 
var sWave
var mWave
var hWave
 
var sChunks
var mChunks
 
function setup() {
  createCanvas(720, 720);
  sWave = new p5.Oscillator()
  mWave = new p5.Oscillator()
  hWave = new p5.Oscillator()
 
  sWave.setType("sin")
  sWave.amp(0.2)
  sWave.freq(second()*25)
 
  mWave.setType("triangle")
  mWave.amp(0.3)
  mWave.freq(minute()*20)
 
  hWave.setType("saw")
  hWave.amp(0.5)
  hWave.freq(hour()*5)
 
  sChunks = new SlimeChunks(0, 0)
  mChunks = new SlimeChunks(0, 0)
  hChunks = new SlimeChunks(0, 0)
  sWave.start()
  mWave.start()
  hWave.start()
}
 
function draw() {
  sWave.freq(second()*25)
  mWave.freq(minute()*10)
  hWave.freq(hour()*5)
 
  var s = second()
  var m = minute()
  var h = hour()
  background(0, 30)
  DrawTallyYear()
 
  var sPeriod = measure/second()
  var mPeriod = measure/minute()
  var hPeriod = measure/hour()
  var angle = map(millis()%(1000*measure), 0, 1000, 0, TWO_PI)
 
  var sXPos = map(millis()%(measure*1000), 0, measure*1000, -85, width+85)
  var mXPos = map(millis()%(measure*1000), 0, measure*1000, -85, width+85)
  var hXPos = map(millis()%(measure*1000), 0, measure*1000, -85, width+85)
 
  var sYPos = height/2 + 25*sin(angle/sPeriod)+(2*size)
  var mYPos = height/2 + 25*sin(angle/mPeriod)+0
  var hYPos = height/2 + 25*sin(angle/hPeriod)-(2*size)
 
 
 
  noStroke()
 
  fill(0, 255, 20)
  ellipse(sXPos, sYPos*1.5-390, size, size)
  fill(0, 155, 20)
  ellipse(mXPos, mYPos*1.5-170, size, size)
  fill(0, 135, 20)
  ellipse(hXPos, hYPos*1.5+30, size, size)
  fill(40)
  blendMode(HARD_LIGHT)
  ellipse(width/2, height/2-190, size*0.8, size*0.8)
  ellipse(width/2, height/2, size*0.8, size*0.8)
  ellipse(width/2, height/2+210, size*0.8, size*0.8)
 
	if(sYPos<=(height/2 + -25+(2.01*size)) ) {
		sChunks.AddChunk()
  }
  if(mYPos<=(height/2-24.9) ) {
		mChunks.AddChunk()
  }
 
  sChunks.DrawChunks()
  sChunks.UpdatePos(sXPos, sYPos)
  sChunks.UpdateChunks()
 
  mChunks.DrawChunks()
  mChunks.UpdatePos(mXPos, mYPos+75)
  mChunks.UpdateChunks()
}
 
function DrawTallyYear() {
	var tallies = floor(2018/5)
  var rem = 2018%5
  var cellSize = 100
  push()
  translate(-60, 40)
  for(var i=0; i<width; i+=cellSize) {
		for(var j=0; j<height; j+=cellSize) {
			DrawTallyMark(i, j, 5, 10)
    }
  }
  pop()
}
 
function DrawTallyMark(x, y, c, w) {
	for(var i=0; i<c*w; i+=w) {
    stroke(135)
		line(x+i+map(random(), 0, 1, -2, 2), y+map(random(), 0, 1, -2, 2), 
        x+i+map(random(), 0, 1, -2, 2), y+c*w+map(random(), 0, 1, -2, 2))
  }
  line(x-w+map(random(), 0, 1, -2, 2), y-w+map(random(), 0, 1, -2, 2), 
    	 x+c*w+map(random(), 0, 1, -2, 2), y+c*w+map(random(), 0, 1, -2, 2))
}
 
function SlimeChunks(x, y, amount=1) {
	this.x = x
  this.y = y
  this.amount = amount
  this.chunks = []
 
  this.AddChunk = function() {
		this.chunks.push([this.x+round(random()*25), this.y]) 
  }
 
  this.DrawChunks = function() {
		for(i=0; i<this.chunks.length; i++) {
      fill(0, 140, 0)
			ellipse(this.chunks[i][0], this.chunks[i][1], 10, 10) 
    }
  }
 
  this.UpdatePos = function(x, y) {
		this.x = x
    this.y = y
  }
 
  this.UpdateChunks = function() {
		for(i=0; i<this.chunks.length; i++) {
      this.chunks[i][0] -= noise(this.chunks[i][0])
      this.chunks[i][1] += 3
    }
  }
}

For my interpretation of the Clocks I wanted to create a sonic representation of time. Time is almost exclusively expressed visually, through clocks, sundials, or some other perpetual accumulation of visual data. In the context of sound though, even in a simple interpretation like mine, there's a tension of evolving dissonance and harmony; some points in time are pleasing to experience while others are unsettling and strange. In retrospect, this project would have been stronger had I left the visual component out altogether. In my rush to explore how far I could push different representations of oscillation I undermined my original idea, that being the audio representation of time.