chewie-Reading03

I would say my interests align more with first word art than last word art. I love experimenting with mediums and disrespecting established form, however I also love exploiting the invisibly standard characteristics of the medium I'm working in. In a lot of my work I have attempted to use the barrier between physical matter and conscious realization to allow the audience a very self-aware and honest experience.

The ways in which technology shapes culture are easy to think about conceptually, but it's very difficult to practically realize where this shaping takes place. Because we use technology as a tool of expression, the limits of that technology become the limits of expression which become the limits of the resulting ideas that manifest themselves as culture and reality. As we add dimensions of complexity to the tools we're working with, so do we add dimensions of complexity to what is expressed through those tools. Audiences accepted the first videos as partial fact, as if the filmmaker (often personally invisible from their perspective) was an omnipotent dictator of reality. With the introduction of role playing video games this illusion of authority becomes much more complicated with many more layers of oblivion to the form. Because the action is up to you, you are forced to operate under a specific set of unflinchingly rigid laws, tuning your brain to a specific set of functions through repeated action. The game controls your mindset completely because it is yourself who is being expressed in their fabricated reality, rather than you watching someone else express themselves.

Culture shapes new technology in the sense that it decides what we pay attention to. Our minds operate based on a defined set of existing ideas. If technology is the manifestation of those ideas in a practical, operational system, technology caters only to culture and the benefit of the human experience. It builds upon what came before and is in fact our culture. I believe technology is culture and therefore it is inherently shaped by the manipulation of culture.

When a new technology is developed, the new abilities of those working with that technology become the center of attention as substantial features. As technology ages and becomes more familiar to us, these new abilities become standard and insignificant. The flashing lights and spectacular explosions that caught our attention become nothing more than a quiet, blank slate to jot ideas on. As we become more capable, our efforts of the past become more frivolous, and we almost feel embarrassed to have put so much time into seemingly "nothing".

nannon-reading03

At first thought, I want to say that my interests lie with last word art. There's something extremely daunting about making something that radically transforming from a medium or concept that most people already take for granted. At the same time, people around me are often very first-word oriented; looking at my recent work for this class, I believe that I've been drawn into the first-word realm of things.

Technology has shaped culture at almost every step of the way--of course, the obvious examples are things such as agriculture, industrial revolution, the internet, etc. But something I think that is very interesting happening in our culture today is the way the internet has shaped very different cultures in the West and in China. Technology today being tied up largely in software has also widened an intellectual + educational gap, exacerbating existing income/wage/socioeconomic gaps. On the flip side, I think small sub cultures can drive technologies as well. For example, niche music and art tech is drive by those who need it and make the case and perhaps build it.

The phrase technologically novel immediately brings to mind countless examples that exist in today's world. AR/VR, blockchain, etc are examples of technologies that people are working immensely hard to try to find uses for, but 99% of what's made/exists will not be remembered beyond the year its made. However, I do think that this kind of research will influence the 1% of products that are the last word, and that the last word wouldn't have existed without the first word. In addition, obsoletism has never been more pervasive than today, and software changes so fast that the foundational value of products can't be based purely on its technology. I think last word stuff is not only technologically novel, but also conceptually, philosophically, and aesthetically challenging.

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);
  }
}

Spoon-Reading03

Michael Naimark brings  up the idea of first and last word art. To him, first word art is that which forges a new path in a new direction, whereas last word art takes already existing knowledge and presents it in a new way. As an artist myself who has taken part in both of these forms of art production, I can say that I don't have much of a strong preference either way. I see the benefit of both, as it is necessary for art to go in new directions sometimes, yet other times it's important to take something that has already been done and expand on that. Art is a personal dialogue with the piece, and either first or last word art brings the opportunity for that. Specifically with last word art, it is sometimes necessary to change the conversation that already exists around something, and only last word art is the place where it is easy to see the changing dialogue in progress.

paukparl-Reading03

This is a similar kind of question to that I've been asking myself for a long time. I believe there is often a dichotomy between hi-tech and low-tech that divides form of practice, people's tastes, and even school curriculums. I wasn't sure which of two I should side on, (I felt that both groups of people were often disapproving of each other) until recently when I started programming, and found out this was something I wanted to do.

And I very much agree with how the author closed his essay. I believe newer tools should be harnessed by more finesse. An unconditional endorsement of established tastes and rules bares some risk of falling into mannerism. And a careless collection of "novel" expressions often has a low signal-to-noise ratio, or seems to have wasted good resource. I think, by staying self-critical at the same time as being inventive and progressive, one could achieve both first and last word art.

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);
  }
}