Spoon-Body

https://youtu.be/H_0f3B_2JLY

This project ended up being more of an exploration of motion than an art piece. The goal was to take a motion capture skeleton and have it interact with some physics objects -- like springs and particles -- to produce an interesting piece. I achieved about half of that. Using the physics engine found in the Toxiclibs library in Processing, I attached a series of short springs and particles (which look like yellow strings in the video) to various points of the body. When the person moves, the springs move around and respond, accentuating the person's movement.

The affectation on the motion definitely came first. To be honest, the skeleton used in the piece was almost an afterthought. If I had left myself more time, I would have liked to get some motion capture footage of someone doing quick motions that cover a lot of space. This would have likely made the springs move all over the place and produce interesting paths and curves as the person moved.

 

 

import toxi.geom.*;
import toxi.geom.mesh.*;
import toxi.geom.mesh.subdiv.*;
import toxi.geom.mesh2d.*;
import toxi.geom.nurbs.*;
import toxi.physics3d.*;
import toxi.physics3d.behaviors.*;
import toxi.physics3d.constraints.*;
 
public class PBvh
{
  public BvhParser parser;  
 
  private ArrayList controlPoints;
  private ArrayList trailingPoints;
  private Particle finalPoint;
  private ArrayList springs;
  private VerletPhysics3D physics;
 
  private float w = 10;
 
  public PBvh(String[] data) {
    parser = new BvhParser();
    parser.init();
    parser.parse( data );
  }
 
  public void update( int ms ) {
    parser.moveMsTo( ms );//30-sec loop 
    parser.update();
  }
 
  public void setup() {
    controlPoints = new ArrayList();
    trailingPoints = new ArrayList();
    springs = new ArrayList();
    physics = new VerletPhysics3D();
    Vec3D gravity = new Vec3D(0, -10, 0);
    GravityBehavior3D gb = new GravityBehavior3D(gravity);
    physics.addBehavior(gb);
 
    finalPoint = new Particle(0, 0, 0);
 
    for( BvhBone b : parser.getBones())
    {
      if (true/*!b.hasChildren()*/)
      {
        Particle p1;
        ArrayList p2;
        p1 = new Particle(b.absEndPos.x, 
                          b.absEndPos.y, 
                          b.absEndPos.z);
        p2 = new ArrayList();
 
        for(int i = 0; i < 10; i++) {
          Particle p = new Particle(b.absPos.x, 
                                    b.absPos.y, 
                                    b.absPos.z);
          p2.add(p);
        }
 
        p1.lock();
        ArrayList armSprings;
        armSprings = new ArrayList();//new VerletSpring3D(p1, p2.get(0), 5, 0.05);
        armSprings.add(new VerletSpring3D(p1,
                                          p2.get(0),
                                          1,
                                          0.1));
        for(int i = 1; i < 10; i++) {
          VerletSpring3D s = new VerletSpring3D(p2.get(i - 1),
                                                p2.get(i),
                                                1,
                                                0.1);
          armSprings.add(s);
        }
        armSprings.add(new VerletSpring3D(armSprings.get(armSprings.size() - 1)
                                          finalPoint,
                                          1
                                          0.1)
        controlPoints.add(p1);
        physics.addParticle(p1);
        for(Particle p : p2) {
          trailingPoints.add(p);
          physics.addParticle(p);
        }
        for(VerletSpring3D spring : armSprings) {
          springs.add(spring); 
          physics.addSpring(spring);
        }
      } 
    }
  }
 
  public void draw() {
    fill(color(255));
    int counter = 0;
    for( BvhBone b : parser.getBones())
    {
      pushMatrix();
      translate(b.absPos.x, b.absPos.y, b.absPos.z);
      ellipse(0, 0, 2, 2);
      popMatrix();
 
      Particle p1 = controlPoints.get(counter);
      p1.x = b.absPos.x;
      p1.y = b.absPos.y;
      p1.z = b.absPos.z;
      counter++;
 
      if (!b.hasChildren())
      {
        pushMatrix();
        translate(b.absEndPos.x, b.absEndPos.y, b.absEndPos.z);
        println("xbody = ", b.absEndPos.x);
        println("ybody = ", b.absEndPos.y);
        println("zbody = ", b.absEndPos.z);
        ellipse(0, 0, 10, 10);
        popMatrix();  
 
        /*Particle p1 = controlPoints.get(counter);
        p1.x = b.absEndPos.x;
        p1.y = b.absEndPos.y;
        p1.z = b.absEndPos.z;
        counter++;*/
      }
 
    }
 
    physics.update();
    counter = 0;
    /*for(Particle p1 : controlPoints) {
      counter++;
      int bvhCounter = 0;
      for( BvhBone b : parser.getBones()) {
        if(!b.hasChildren()) {
          bvhCounter++;
        }
        if(bvhCounter == counter) {
          p1.lock();
          p1.x = b.absEndPos.x;
          p1.y = b.absEndPos.y;
          p1.z = b.absEndPos.z;
 
          println("xpoint = ", p1.x);
          println("ypoint = ", p1.y);
          println("zpoint = ", p1.z);
        }
      }
    }*/
 
    /*for(Particle p2 : trailingPoints) {
      p2.display();
    }*/
    for(VerletSpring3D spring : springs) {
      Particle p1 = (Particle) spring.a;
      Particle p2 = (Particle) spring.b;
 
      p1.display(2);
      p2.display(2);
 
      float x1 = p1.x;
      float y1 = p1.y;
      float z1 = p1.z;
 
      float x2 = p2.x;
      float y2 = p2.y;
      float z2 = p2.z;
 
      stroke(255, 198, 0);
      line(x1, y1, z1, x2, y2, z2);
    }
  }
}
// Originally from http://perfume-dev.github.io/
 
BvhParser parserA = new BvhParser();
PBvh /*bvh1,*/ /*bvh2,*/ bvh3;
 
public void setup()
{
  size( 1280, 720, P3D );
  background( 0 );
  noStroke();
  frameRate( 30 );
 
//  bvh1 = new PBvh( loadStrings( "A_test.bvh" ) );
//  bvh2 = new PBvh( loadStrings( "B_test.bvh" ) );
  bvh3 = new PBvh( loadStrings( "C_test.bvh" ) );
 
//  bvh1.update(0);
//  bvh2.update(0);
  bvh3.update(0);
 
//  bvh1.setup();
//  bvh2.setup();
  bvh3.setup();
 
  loop();
}
 
public void draw()
{
  background( 0 );
 
  //camera
  float _cos = cos(millis() / 7000.f);
  float _sin = sin(millis() / 5000.f);
  camera(width/8.f + width/8.f /** _sin +200*/, height/2.0f-100, 550 + 150/* * _cos*/, width/2.0f + 400, height/2.0f, -400, 0, 1, 0);
 
  //ground 
  /*fill( color( 255 ));
  stroke(127);
  line(width/2.0f, height/2.0f, -30, width/2.0f, height/2.0f, 30);
  stroke(127);
  line(width/2.0f-30, height/2.0f, 0, width/2.0f + 30, height/2.0f, 0);
  */stroke(255);
 
  pushMatrix();
  translate( width/2, height/2-10, 0);
  scale(-1, -1, -1);
 
  //model
//  bvh1.update( millis() );
//  bvh2.update( millis() );
  bvh3.update( millis() );
 
//  bvh1.draw();
//  bvh2.draw();
  bvh3.draw();
 
  popMatrix();
}
class Particle extends VerletParticle3D {
  Particle(float x, float y, float z) {
    super(x, y, z);
  }

void display(int n) {
pushMatrix();
translate(x, y, z);
//ellipse(0, 0, n, n);
popMatrix();
}
}


	

Spoon-LookingOutwards03

A Journey, Seoul by Mimi Son and Elliot Woods.

Seoul is a piece of interactive art that consists of numerous clear acrylic plates with white detailing added to them. The plates can be placed into a box by the viewer that illuminates the plates and projects colors and patterns onto them. The project focuses on the viewer's memories, calling to viewer to assemble a box that means something to them.

I like how self-defined the piece is. It tries not to be meaningful to the viewer by making  a statement on its own. Instead, its meaning comes from the meaning that the viewer puts into it. This is interactive art on a very base level, i.e., the user has a hand in creating their own version of the piece. In fact, no two viewers will view the same piece. While they both might assemble the same plates in the same order, nobody will assemble the box for the same reason, thus fundamentally changing the piece.

A Journey, Seoul is part of a series created by the artists that includes a couple of other cities. Each city provides different interactions-- for example, A Journey, London, uses changing sounds and lights to tell different stories through the same physical model. Each iteration of the project includes the user more and more in the personalization (A Journey, Dublin is the last in the series and allows the viewer to actually draw on the panels to create their own narrative completely).

Of the series, though, I think Seoul is the strongest. London leaves little up to the viewer, making its interaction a mostly passive experience. Dublin, while quite visually pleasing, seems to give too much freedom to the viewer. The piece becomes more of a white-board than an art piece. Seoul, on the other hand, gives the viewer enough freedom to make their own memories from the predefined plates, while still keeping control over what the viewer is seeing to a certain extent. There is value to limiting what a viewer can do, as it forces the imagination to fill in gaps in their head, rather than giving the viewer the freedom to fill in those gaps in the physical world.

 

 

Spoon-Viewing04

Spectacle can be defined as the category containing computational art created by commercial studios and highly funded collectives that focuses on being impressive through scale, detail, or polish.

Speculation can be defined as the computational art, often academic, that yields more freedom to speak critically about society, while usually relying on technicians hired by the artist to handle the technical aspects of the project.

 

Image result for strandbeest

Theo Jansen's Strandbeests, while not fitting totally cleanly into one category or another, definitely align more closely with spectacle. They are an evolved and evolving species of physical sculpture whose evolution (at least that of the mechanical leg components, as the evolution is a mix of computer generation and educated human choices) is driven by an evolutionary algorithm working towards an ideal walk pattern. The project carries little to no social message, and is rather an exploration in the creation of mechanical life forms. Many of the creatures exist on a massive scale. They are incredibly, sometimes twice or three times the height of a human, and their massive sales wave in the wind. They arrest attention as they walk down beaches (their primary habitat).

By the dichotomies presented in Warburton's video, Strandbeests clearly fall more into the category of spectacle. The sculptures' evolution and release seems to indicate a sense of acceleration rather than dragging. While many of the changes may be small, there are moments of major leaps forward in the sculptures' technology. The pieces are massively visible: they are released in public spaces (beaches), move, are mesmerizing to look at, and they are very large objects. They seem to exist in a surplus: there are a lot of these sculptures at various stages of the evolution, many of them move along the same beaches rather than become waste. In a departure from spectacle, though, the sculptures are most certainly art over commerce. They are not designed to be bought and sold, rather they are designed to be observed and appreciated. Finally, they exist somewhere in between function and dysfunction. While the sculptures do not serve a necessary function in society, every iteration of the Strandbeest introduces some sort of functional mechanical advantage making it a better survivor than the last: better leg part ratios, more efficient methods for capturing energy from the wind, balance or collision avoidance, etc.

Spoon-Telematic

This project was intended to be a collaborative bridge builder. When the user presses and holds the mouse, a line appears, centered on the user's mouse, that is two-thirds the size of the gap between the two ground banks. When the mouse is released, physics will begin applying to the line and it will continue to fall downwards unless it is obstructed by something. Since a user can only place one line at a time and no line is the width of the gap, a single user cannot build a bridge by themselves. In order to build a bridge, there must be multiple users collaborating. When the user logs off, though, all the lines they have set down disappear. Thus, if a user wants a bridge that will survive if all the other users logoff, they will have to lay their lines in such a way that they would be able to support themselves without the other user's presence.

Unfortunately, I was unable to get the application to work. I attempted to use glitch.js to allow for the multiple users to interact with the same scene and p2 as the physics engine. Having trouble with the interactivity and physics, I attempted to simplify the project by having the users stack blocks instead of build bridges. While this simplified the interactivity and graphics little bit, it still involved figuring out how to use glitch and p2 together. Because of the server-side preference in glitch, I was unable to get the physics engine in p2 to work properly, as it has to run in the server rather than the browser.  In order to get this to work, I would have had to take the mouse data from each user in the browser, send it to the server, which would then create the rigid bodies for the lines and calculate their interactions, and then send all of that data back to the browser. In addition to getting the code to compile with the p2 library, I was unable to figure out how to code the transfer of all this data back and forth in this manner.

While it does not compile, I have attached the app:

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.

Spoon-LookingOutwards02

Diffusion Choir by David Wicks is a massive kinetic sculpture consisting of hundreds of independently-actuated folding paper forms. These forms are powered by a flocking algorithm that makes them appear to have the flapping motions of birds. This piece stuck out to me particularly because of its use of an art form that I am newly interested in and an art form that I was interested in a number of years ago: generative art and origami. I used to spend hours trying to fold, with occasionally moderate success, intricate origami models. Recently, though, I have been increasingly interested in algorithmic generative art. Seeing the two combined, both of which begin with relatively simple concepts (bits for generative art and just a piece of paper for the origami) and turning them into something so complex amazes me. My only real piece of criticism for the installation is that the artist seems to emphasize the idea that each item in the installation portrays a bird in his statement for the piece. While I can see the image quite clearly from the bottom, from the side (the piece is designed to be viewed from all sorts of angles), the image of birds gets lost for something far more abstract.

The piece can be viewed here: http://sansumbrella.com/works/2016/diffusion-choir/

Abstracted, repeated image of human face.

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

Spoon-Scope

 

This design takes the bouncing rectangle design from my GIF and applies it to the format of a praxinoscope. The side ratios are slightly different, adjusted only so the rectangle would fill the frame well. The motion in the rectangle is fluid as it uses the same adjustable center cosine window function that is used in the GIF for the rectangle.

See my GIF post for sketches.

praxinoscope-output

 

 

// Template for KidzLabs/4M/Toysmith Animation Praxinoscope
// https://www.amazon.com/4M-3474-Animation-Praxinoscope/dp/B000P02HYC
// https://www.walmart.com/ip/Animation-Praxinoscope-Science-Kits-by-Toysmith-3474/45681503
// Developed for Processing 3.3.6 * http://processing.org
// 23 January 2018 * Golan Levin 
 
// See information about Processing PDF export at: 
// https://processing.org/reference/libraries/pdf/index.html
// PDF generated by Processing can be opened in Adobe Illustrator.
import processing.pdf.*;
boolean bRecordingPDF = false;
 
float inch = 72; 
float diamArtInner = inch * 1.50; 
float diamArtOuter = inch * 4.80; 
float diamCutInner = inch * 1.41; 
float diamCutOuter = inch * 4.875; 
float holeDy = inch * 0.23;
float holeDx = inch * 0.20;
float holeD = inch * 0.1;
 
final int nFrames = 10; 
int myFrameCount = 0;
int exportFrameCount = 0; 
boolean bAnimate = true; 
boolean bExportFrameImages = false;
 
//-------------------------------------------------------
void setup() {
  size(792, 612); // 11x8.5" at 72DPI
  frameRate(15);
  smooth();
} 
 
//-------------------------------------------------------
void draw() {
  background(240); 
  if (bRecordingPDF) {
    beginRecord(PDF, "praxinoscope-output.pdf");
  }
 
  // Do all the drawing. 
  pushMatrix(); 
  translate(width/2, height/2);
  drawCutLines(); 
  drawGuides(); 
  drawAllFrames();
  popMatrix();
 
  if (bExportFrameImages) {
    // If activated, export .PNG frames 
    if (exportFrameCount &lt; nFrames) { String filename = "frame_" + nf((exportFrameCount%nFrames), 3) + ".png"; saveFrame("frames/" + filename); println("Saved: " + filename); exportFrameCount++; if (exportFrameCount &gt;= nFrames) {
        bExportFrameImages = false;
        exportFrameCount = 0;
      }
    }
  }
 
  if (bRecordingPDF) {
    endRecord();
    bRecordingPDF = false;
  }
}
 
 
//-------------------------------------------------------
void keyPressed() {
  switch (key) {
  case ' ': 
    // Press spacebar to pause/unpause the animation. 
    bAnimate = !bAnimate;
    break;
 
  case 'p': 
  case 'P':
    // Press 'p' to export a PDF for the Praxinoscope.
    bRecordingPDF = true; 
    break;
 
  case 'f': 
  case 'F': 
    // Press 'f' to export .png Frames (to make an animated .GIF)
    myFrameCount = 0; 
    exportFrameCount = 0; 
    bExportFrameImages = true;
    bAnimate = true; 
    break;
  }
}
 
//-------------------------------------------------------
void drawCutLines() {
  fill(0); 
  textAlign(CENTER, BOTTOM); 
  text("Praxinoscope Template", 0, 0-diamCutOuter/2-6); 
 
  stroke(0); 
  strokeWeight(1.0);
 
  noFill(); 
  if (!bRecordingPDF) {
    fill(255); 
  }
  ellipse(0, 0, diamCutOuter, diamCutOuter);
 
  noFill(); 
  if (!bRecordingPDF) {
    fill(240); 
  }
  ellipse(0, 0, diamCutInner, diamCutInner);
 
  noFill(); 
  ellipse(diamCutOuter/2 - holeDx, 0-holeDy, holeD, holeD); 
 
  line (diamCutInner/2, 0, diamCutOuter/2, 0);
}
 
//-------------------------------------------------------
void drawGuides() {
  // This function draws the guidelines. 
  // Don't draw these when we're exporting the PDF. 
  if (!bRecordingPDF) {
 
    noFill(); 
    stroke(128); 
    strokeWeight(0.2); 
    ellipse(0, 0, diamArtInner, diamArtInner); 
    ellipse(0, 0, diamArtOuter, diamArtOuter);
 
    for (int i=0; i&lt;nFrames; i++) {
      float angle = map(i, 0, nFrames, 0, TWO_PI); 
      float pxi = diamArtInner/2 * cos(angle);
      float pyi = diamArtInner/2 * sin(angle);
      float pxo = diamArtOuter/2 * cos(angle);
      float pyo = diamArtOuter/2 * sin(angle);
      stroke(128); 
      strokeWeight(0.2);
      line (pxi, pyi, pxo, pyo);
    }
 
    // Draw the red wedge outline, highlighting the main view.
    int redWedge = 7; // assuming nFrames = 10
    for (int i=redWedge; i&lt;=(redWedge+1); i++) {
      float angle = map(i, 0, nFrames, 0, TWO_PI); 
      float pxi = diamArtInner/2 * cos(angle);
      float pyi = diamArtInner/2 * sin(angle);
      float pxo = diamArtOuter/2 * cos(angle);
      float pyo = diamArtOuter/2 * sin(angle);
      stroke(255, 0, 0); 
      strokeWeight(2.0);
      line (pxi, pyi, pxo, pyo);
    }
    noFill(); 
    stroke(255, 0, 0); 
    strokeWeight(2.0);
    float startAngle = redWedge*TWO_PI/nFrames;
    float endAngle = (redWedge+1)*TWO_PI/nFrames;
    arc(0, 0, diamArtInner, diamArtInner, startAngle, endAngle); 
    arc(0, 0, diamArtOuter, diamArtOuter, startAngle, endAngle); 
 
 
    for (int i=0; i&lt;nFrames; i++) {
      float angle = map(i, 0, nFrames, 0, TWO_PI); 
 
      pushMatrix();
      rotate(angle); 
      float originY = ((diamArtOuter + diamArtInner)/2)/2;
      translate(0, 0-originY); 
 
      noFill(); 
      stroke(128); 
      strokeWeight(0.2);
      line (-inch/2, 0, inch/2, 0); 
      line (0, -inch/2, 0, inch/2); 
 
      popMatrix();
    }
  }
}
 
//-------------------------------------------------------
void drawAllFrames() {
  for (int i=0; i&lt;nFrames; i++) {
    float angle = map(i, 0, nFrames, 0, TWO_PI); 
    float originY = ((diamArtOuter + diamArtInner)/2)/2;
 
    pushMatrix();
    rotate(angle); 
    translate(0, 0-originY); 
    scale(0.8, 0.8); // feel free to ditch this 
 
    int whichFrame = i; 
    if (bAnimate) {
      whichFrame = (i+myFrameCount)%nFrames;
    }
    drawArtFrame (whichFrame); 
    // drawArtFrameAlternate (whichFrame); 
 
    popMatrix();
  }
  myFrameCount++;
}
 
 
//-------------------------------------------------------
void drawArtFrame (int whichFrame) { 
  // Draw the artwork for a generic frame of the Praxinoscope, 
  // given the framenumber (whichFrame) out of nFrames.
  // NOTE #1: The "origin" for the frame is in the center of the wedge.
  // NOTE #2: Remember that everything will appear upside-down!
 
  fill(0); 
  noStroke(); 
 
  float multiplier2 = function_AdjustableCenterCosineWindow(
                                              map(whichFrame, 
                                              0, nFrames, 0, 1), 
                                              0.3);
  drawCenteredRectangleFromBottom(0, 30, 
                                  map(multiplier2, 0, 1, 35, 65),
                                  map(multiplier2, 0, 1, 100, 50));
  noFill();
  stroke(255);
  strokeWeight(5);
}
 
//-------------------------------------------------------
 
void drawCenteredRectangleFromBottom(float x, float y, 
                            float width, float height) {
  float upperLeftX = x - (width / 2);
  float upperLeftY = height - y;
  rect(upperLeftX, upperLeftY, width, -height);
}
 
float function_AdjustableCenterCosineWindow (float x, float a) {
  // functionName = "Adjustable Center Cosine Window";
 
  float ah = a/2.0; 
  float omah = 1.0 - ah;
 
  float y = 1.0;
  if (x &lt;= a) {
    y = 0.5 * (1.0 + cos(PI* ((x/a) - 1.0)));
  } 
  else {
    y = 0.5 * (1.0 + cos(PI* (((x-a)/(1.0-a))  )));
  } 
  return y;
}

Spoon-Reading02

1A. Strandbeests, the gigantic sculptures created by Theo Jansen that use wind power to walk up and down the beaches of the Netherlands are an example of effective complexity. They exhibit qualities of both randomness and order. The mechanism for the legs was developed through an evolution simulation (randomness). The geometry of the legs is rather simple, but when assembled together, the legs create an intricate walking thing. There is a formal order to the mechanics of the creatures, which lends itself to the graceful movements of the sculptures. In this case, the randomness led to an order, mimicking our own development as a species.  Some of the Strandbeests even feature abilities to respond to their environments -- they are able to turn away from water or anchor themselves in the case of too much wind.

 

1B. The problem of uniqueness: As mentioned in the essay, part of the value of art for a long time has been its one-of-a-kind nature. While I understand that rarity has value, I think we often place too much emphasis on it. Valuable art is art that you can develop a connection with. It doesn't seem particularly important whether everybody else also has access to the same art. In fact, I think art has the potential to be even more powerful when it can be mass produced. If a piece of art can be mass produced, whether each piece is unique as is the case for some pieces of generative art, more people can access it, and, therefore, more people have the opportunity to derive meaning from it. If enough people derive meaning from a piece, that piece might have the power to effect society on a macro scale.

Spoon-AnimatedLoop

 

 

With this project I set out to make a GIF consisting of flat graphics that illustrated some sort of mechanical interaction. I experimented with a couple of ideas first through sketching before settling on the idea of rectangles that sucked up and expelled circles.

I started with the rectangle and used the adjustable center cosine window function for the expansion and contraction of the rectangle. This gave me a smooth stretching of the rectangle before a quick contraction.

I attempted first to use this same function for the movement of the circles, but I switched to the double elliptic sigmoid, which has a slow beginning and a very fast middle before slowing down in the end. Fiddling with the constants in these two functions allowed me to get the movements to sync up as if the circles were being shot out of the rectangles.

Next I doubled the rows of rectangles and circles and moved the first row to the top half of the image. This created an image that filled the square better (it wasn't as bottom heavy as the previous version), and it also created an image that could be tiled, which provides an indication of spacial unendingness to the animation.

The constant linear movement rectangles and circles was distracting and not particularly enjoyable to watch, so I experimented with a number of different mathematical functions for the movement.

I settled on the double odd polynomial ogee function, which has a very large flat section in its curve that brings the movement of the rectangles to a complete halt. This function gave me the freedom to invert the direction of the circles, which now moved in the opposite direction of the rectangles while both stopped to interact with each other. After settling on the movement, I experimented with a lot of different color schemes for the animation. I decided to go with the black-and-grey color scheme with changing yellow and red circles. The colors evoked a more industrial feel to the image. The changing from grey to white in the rectangles as they expand and contract helps to indicate stretching, like the discoloration of plastics and rubbers when they are stretched. The changing from red to yellow indicates change in state for the circles as they pass through the system, almost like the cooling off and heating up of metal.

I am satisfied with the movement of the image, although I am not as sure about the coloration. I liked the black and red as a graphic composition, but I also like the references that the final color scheme indicate to. I also wonder if my composition might have been stronger with a larger, zoomed in view of a single row. When untiled, the top and bottom rows appear less intriguing than the central one.

 

 

// This is a template for creating a looping animation in Processing/Java. 
// When you press the 'F' key, this program will export a series of images
// into a "frames" directory located in its sketch folder. 
// These can then be combined into an animated gif. 
// Known to work with Processing 3.4
// Prof. Golan Levin, January 2018
 
//===================================================
// Global variables. 
String  myNickname = "Spoon"; 
int     nFramesInLoop = 180;
int     nElapsedFrames;
boolean bRecording; 

//===================================================
void setup() {
  size (640, 640); 
  bRecording = false;
  nElapsedFrames = 0;
}

//===================================================
void keyPressed() {
  if ((key == 'f') || (key == 'F')) {
    bRecording = true;
    nElapsedFrames = 0;
  }
}
 
//===================================================
void draw() {
 
  // Compute a percentage (0...1) representing where we are in the loop.
  float percentCompleteFraction = 0; 
  if (bRecording) {
    percentCompleteFraction = (float) nElapsedFrames / (float)nFramesInLoop;
  } else {
    percentCompleteFraction = (float) (frameCount % nFramesInLoop) / (float)nFramesInLoop;
  }
 
  // Render the design, based on that percentage. 
  renderMyDesign (percentCompleteFraction);
 
  // If we're recording the output, save the frame to a file. 
  if (bRecording) {
    saveFrame("frames/" + myNickname + "_frame_" + nf(nElapsedFrames, 4) + ".png");
    nElapsedFrames++; 
    if (nElapsedFrames >= nFramesInLoop) {
      bRecording = false;
    }
  }
}


//===================================================
void renderMyDesign (float percent) {
  //
  // YOUR ART GOES HERE.
  // This is an example of a function that renders a temporally looping design. 
  // It takes a "percent", between 0 and 1, indicating where we are in the loop. 
  // This example uses two different graphical techniques. 
  // Use or delete whatever you prefer from this example. 
  // Remember to SKETCH FIRST!
 
  //----------------------
  // here, I set the background and some other graphical properties
  background (0, 0, 0);
  noStroke();
  fill (75, 75, 75);
  rect (0, height / 4 - 15, width, 2 * height / 4);
  smooth(); 
  noStroke(); 
 
  //----------------------
  // Here, I assign some handy variables. 
  float cx = 100;
  float cy = 100;
 
 //----------------------
  int quantity = 8;
  float reducedPercent = map(percent < 0.75 ? 0 : percent - 0.75, 0, 0.25, 0, 1);
  float invertedPercent = percent <= .5 ? percent + 0.5 : percent - 0.5;
  float invertedReducedPercent = map(invertedPercent < 0.75 ? 0 : invertedPercent - 0.75, 0, 0.25, 0, 1);
  float multiplier1 = function_DoubleEllipticSigmoid(reducedPercent, 0.8, 0.4);
  float invertedMultiplier1 = function_DoubleEllipticSigmoid(invertedReducedPercent, 0.8, 0.4);
  float multiplier2 = function_AdjustableCenterCosineWindow(percent, 0.8);
  float invertedMultiplier2 = function_AdjustableCenterCosineWindow(invertedPercent, 0.8);
  float positionMultiplier = map(function_DoubleOddPolynomialOgee (percent > 0.15 ? percent - 0.15 : percent + 0.85, 0.5, 0.5, 6),
        0, 1, 0, 0.25);
  for(int i = -quantity; i <= quantity; i++) {
    if(i % 2 == 0) {
      fill(map(percent, 0, 1, 255, 150), map(percent, 0, 1, 198, 0), map(percent, 0, 1, 0, 35));
      ellipse(i * width / quantity + map(positionMultiplier, 0, 1, width, 0), map(multiplier1, 0, 1, 30, -height / 2 + 30), 50, 50);
      fill(map(percent, 0, 1, 150, 255), map(percent, 0, 1, 0, 198), map(percent, 0, 1, 35, 0));
      ellipse(i * width / quantity + map(positionMultiplier, 0, 1, width, 0), map(multiplier1, 0, 1, height / 2 + 30, 30), 50, 50);
      fill(map(percent, 0, 1, 255, 150), map(percent, 0, 1, 198, 0), map(percent, 0, 1, 0, 35));
      ellipse(i * width / quantity + map(positionMultiplier, 0, 1, width, 0), map(multiplier1, 0, 1, height + 30, height / 2 + 30), 50, 50);
    } else {
      fill(map(invertedPercent, 0, 1, 255, 150), map(invertedPercent, 0, 1, 198, 0), map(invertedPercent, 0, 1, 0, 35));
      ellipse(i * width / quantity + map(positionMultiplier, 0, 1, width, 0), map(invertedMultiplier1, 0, 1, 30, -height / 2 + 30), 50, 50);
      fill(map(invertedPercent, 0, 1, 150, 255), map(invertedPercent, 0, 1, 0, 198), map(invertedPercent, 0, 1, 35, 0));
      ellipse(i * width / quantity + map(positionMultiplier, 0, 1, width, 0), map(invertedMultiplier1, 0, 1, height / 2 + 30, 30), 50, 50);
      fill(map(invertedPercent, 0, 1, 255, 150), map(invertedPercent, 0, 1, 198, 0), map(invertedPercent, 0, 1, 0, 35));
      ellipse(i * width / quantity + map(positionMultiplier, 0, 1, width, 0), map(invertedMultiplier1, 0, 1, height + 30, height / 2 + 30), 50, 50);
    }
  }
   
  //----------------------
  for(int i = -quantity; i <= quantity; i++) {
    if(i % 2 == 0) {
      fill(map(multiplier2, 0, 1, 150, 255));
      drawCenteredRectangleFromBottom(i * width / quantity + map(positionMultiplier, 0, 1, 0, width), height, 
                                    map(multiplier2, 0, 1, 75, 50), 100 - map(multiplier2, 0, 1, 50, 0));
      drawCenteredRectangleFromBottom(i * width / quantity + map(positionMultiplier, 0, 1, 0, width), height / 2, 
                                    map(multiplier2, 0, 1, 50, 75), 100 - map(multiplier2, 0, 1, 0, 50));
    } else {
      fill(map(invertedMultiplier2, 0, 1, 150, 255));
      drawCenteredRectangleFromBottom(i * width / quantity + map(positionMultiplier, 0, 1, 0, width), height, 
                                    map(invertedMultiplier2, 0, 1, 75, 50), 100 - map(invertedMultiplier2, 0, 1, 50, 0));
      drawCenteredRectangleFromBottom(i * width / quantity + map(positionMultiplier, 0, 1, 0, width), height / 2, 
                                    map(invertedMultiplier2, 0, 1, 50, 75), 100 - map(invertedMultiplier2, 0, 1, 0, 50));
    }
  }
}
 
//===================================================
// Functions I wrote that are called in renderMyFunction

void drawCenteredRectangleFromBottom(float x, float y, float width, float height) {
  float upperLeftX = x - (width / 2);
  float upperLeftY = y - height;
  rect(upperLeftX, upperLeftY, width, height);
}
 
//===================================================
// Mathematical functions called in renderMyDesign
// Taken from https://github.com/golanlevin/Pattern_Master

float function_AdjustableCenterCosineWindow (float x, float a) {
  // functionName = "Adjustable Center Cosine Window";
  
  float ah = a/2.0; 
  float omah = 1.0 - ah;

  float y = 1.0;
  if (x <= a) {
    y = 0.5 * (1.0 + cos(PI* ((x/a) - 1.0)));
  } 
  else {
    y = 0.5 * (1.0 + cos(PI* (((x-a)/(1.0-a))  )));
  } 
  return y;
}

float function_DoubleEllipticSigmoid (float x, float a, float b){
  // functionName = "Double-Elliptic Sigmoid";

  float y = 0;
  if (x<=a){
    if (a <= 0){
      y = 0;
    } else {
      y = b * (1.0 - (sqrt(sq(a) - sq(x))/a));
    }
  } 
  else {
    if (a >= 1){
      y = 1.0;
    } else {
      y = b + ((1.0-b)/(1.0-a))*sqrt(sq(1.0-a) - sq(x-1.0));
    }
  }
  return y;
}

float function_DoubleOddPolynomialOgee (float x, float a, float b, int n) {
  //functionName = "Double Odd-Polynomial Ogee";

  float min_param_a = 0.0 + EPSILON;
  float max_param_a = 1.0 - EPSILON;
  float min_param_b = 0.0;
  float max_param_b = 1.0;

  a = constrain(a, min_param_a, max_param_a); 
  b = constrain(b, min_param_b, max_param_b); 
  int p = 2*n + 1;
  float y = 0;
  if (x <= a) {
    y = b - b*pow(1-x/a, p);
  } 
  else {
    y = b + (1-b)*pow((x-a)/(1-a), p);
  }
  return y;
}