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