chewie-AnimatedLoop

My original goal in making this gif was to familiarize myself with loading animating picture files in p5.js. I started with a simple animation sequence that I processed in photoshop to isolate each element.

 

The hardest part was getting different cells to animate the frames at a different offset interval. The solution I used wasn't very elegant but in the future I think I'll set up some sort of data structure for frames that are being animated.

I used the PennerEaseInOutBack easing function to shift the entire frame of cells down smoothly. I think the smooth sweeping motion of the frame looks nice against the choppier, hand-drawn animation. The way it stretches slightly past the end point also makes the motion look like waves crashing. What I like about the gif is the way that the hand-drawn animations works with the perfect grid layout and smooth sweeping animation. The combination of those two qualities makes it satisfying for me to watch.

If I were to spend more time on it I would get more creative with the way that the animations are triggered. There are probably more interesting patterns and different ways activate the animation sequences of the folding papers.

 

// This is a template for creating a looping animation in p5.js (JavaScript). 
// This is a template for creating a looping animation in p5.js (JavaScript). 
// When you press the 'F' key, this program will export a series of images into
// your default Downloads folder. These can then be made into an animated gif. 
// This code is known to work with p5.js version 0.6.0
// Prof. Golan Levin, 28 January 2018
 
// INSTRUCTIONS FOR EXPORTING FRAMES (from which to make a GIF): 
// 1. Run a local server, using instructions from here:
//    https://github.com/processing/p5.js/wiki/Local-server
// 2. Set the bEnableExport variable to true.
// 3. Set the myNickname variable to your name.
// 4. Run the program from Chrome, press 'f'. 
//    Look in your 'Downloads' folder for the generated frames.
// 5. Note: Retina screens may export frames at twice the resolution.
 
 
//===================================================
// User-modifiable global variables. 
var myNickname = "nickname";
var nFramesInLoop = 120;
var bEnableExport = true;
 
// Other global variables you don't need to touch.
var nElapsedFrames;
var bRecording;
var theCanvas;
var frames = [];
var frameN = 0;
var unit;
var activeFrames = [];
var framesToDeact = [];
var levels = [[[4,4],[4,5],[5,4],[4,3],[3,4]],
 
               [[4,2],[3,2],[3,3],[2,3],[2,4],[2,5],[3,5],[3,6],[4,6],
                [5,6],[5,5],[6,5],[6,4],[6,3],[5,3],[5,2]],
 
               [[4,1],[3,1],[2,1],[2,2],[1,2],[1,3],[1,4],[1,5],[1,6],
                [2,6],[2,7],[3,7],[4,7],[5,7],[6,7],[6,6],[7,6],[7,5],
                [7,4],[7,3],[7,2],[6,2],[6,1],[5,1]],
 
               [[3,0],[2,0],[1,0],[1,1],[0,1],[0,2],[0,5],[0,6],[0,7],
                [1,7],[1,8],[2,8],[5,8],[6,8],[7,8],[7,7],[8,7],[8,6],
                [8,3],[8,2],[8,1],[7,1],[7,0],[6,0]],
 
               [[0,0],[0,3],[0,4],[0,8],[3,8],[4,8],[8,8],[8,5],[8,4],
                [8,0],[5,0],[4,0]]];
 
 
 
var levels1 = [[[4,4],[4,5],[5,4],[5,5]],
 
              [[3,3],[3,4],[3,5],[3,6],[4,6],[5,6],[6,6],[6,5],[6,4],
               [6,3],[5,3],[4,3]],
 
              [[2,2],[2,3],[2,4],[2,5],[2,6],[2,7],[3,7],[4,7],[5,7],
               [6,7],[7,7],[7,6],[7,5],[7,4],[7,3],[7,2],[6,2],[5,2],
               [4,2],[3,2]],
 
              [[1,1],[1,2],[1,3],[1,4],[1,5],[1,6],[1,7],[1,8],[2,8],
               [3,8],[4,8],[5,8],[6,8],[7,8],[8,8],[8,7],[8,6],[8,5],
               [8,4],[8,3],[8,2],[8,1],[7,1],[6,1],[5,1],[4,1],[3,1],
               [2,1]],
 
              [[0,0],[0,1],[0,2],[0,3],[0,4],[0,5],[0,6],[0,7],[0,8],
               [0,9],[1,9],[2,9],[3,9],[4,9],[5,9],[6,9],[7,9],[8,9],
               [9,9],[9,8],[9,7],[9,6],[9,5],[9,4],[9,3],[9,2],[9,1],
               [9,0],[8,0],[7,0],[6,0],[5,0],[4,0],[3,0],[2,0],[1,0]]];
 
 
//===================================================
function setup() {
  theCanvas = createCanvas(1920, 1920);
  bRecording = false;
  nElapsedFrames = 0;
  loadFrames();
  unit = width/9;
}
 
//===================================================
function keyTyped() {
  if (bEnableExport) {
    if ((key === 'f') || (key === 'F')) {
      bRecording = true;
      nElapsedFrames = 0;
      frameN = 0;
    }
  }
}
 
//===================================================
function draw() {
 
  // Compute a percentage (0...1) representing where we are in the loop.
  var percentCompleteFraction = 0;
  if (bRecording) {
    percentCompleteFraction = float(nElapsedFrames) / float(nFramesInLoop);
  } else {
    percentCompleteFraction = float(frameCount % nFramesInLoop) / float(nFramesInLoop);
  }
 
  // Render the design, based on that percentage. 
  // This function renderMyDesign() is the one for you to change. 
  renderMyDesign (percentCompleteFraction);
 
  // If we're recording the output, save the frame to a file. 
  // Note that the output images may be 2x large if you have a Retina mac. 
  // You can compile these frames into an animated GIF using a tool like: 
  if (bRecording && bEnableExport) {
    var frameOutputFilename = myNickname + "_frame_" + nf(nElapsedFrames, 4) + ".png";
    print("Saving output image: " + frameOutputFilename);
    saveCanvas(theCanvas, frameOutputFilename, 'png');
    nElapsedFrames++;
    frameN ++;
 
    if (nElapsedFrames >= nFramesInLoop) {
      bRecording = false;
    }
  }
}
 
//===================================================
function renderMyDesign (percent) {
  if (percent >0.95) activeFrames = [];
  // THIS IS WHERE YOUR ART GOES. 
  // 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. 
  // Use, modify, or delete whatever you prefer from this example. 
  // This example uses several different graphical techniques. 
  // Remember to SKETCH FIRST!
 
  //----------------------
  // here, I set the background and some other graphical properties
  background(0);
  smooth();
  stroke(0, 0, 0);
  strokeWeight(3);
  var inc = nElapsedFrames%6;
  var offset = height*function_PennerEaseInOutBack (percent);
  for (var row=0; row<9; row++) {
    for (var col=0; col<9; col++) {
      //offset=0;
      image(frames[(nElapsedFrames+row+col)%4],col*unit,row*unit+offset,unit,unit);
      image(frames[(nElapsedFrames+row+col)%4],col*unit,row*unit+offset-height,unit,unit);
    }
  }
  if (inc==0&&nElapsedFrames<30) {
    //start next tier of animation
    animateTier(int(nElapsedFrames/6));
  }
  drawActiveFrames(offset);
  for (var i=0; i<activeFrames.length; i++){//animate the active frames;
    activeFrames[i][2] = (activeFrames[i][2]+1)%frames.length;
    //if (activeFrames[i][2]>frames.length) framesToDeact.push(activeFrames[i]);
  }
 
}
 
// symmetric double-element sigmoid function (a is slope)
// See https://github.com/IDMNYU/p5.js-func/blob/master/lib/p5.func.js
// From: https://idmnyu.github.io/p5.js-func/
//===================================================
 
function loadFrames() {
  var fileName;
  for (var i=0; i<12; i++) {
    fileName = join(["frames/sqr_",str(i+1),".png"],"");
    frames.push(loadImage(fileName));
  }
  for (i=11; i>-1; i--) {
    fileName = join(["frames/sqr_",str(i+1),".png"],"");
    frames.push(loadImage(fileName));
  }
}
 
function animateTier(x) {
  var tier = levels[x];
  var xyt;
  var start = 0;
  for (var i=0; i<tier.length; i++) {
    xyt = [tier[i][0], tier[i][1], 0];
    activeFrames.push(xyt);
  }
}
 
function removeXYT(xyt, l) {
  for (var i=0; i<l.length; i++) {
    if (xyt[0]==l[i][0] && xyt[1]==l[i][1]) {
      l.pop(i);
      return;
    }
  }
  return;
}
 
function drawActiveFrames(offset) {
  var af;
  for (var i=0; i<activeFrames.length; i++) {
    af = activeFrames[i];
    //offset=0;
    image(frames[af[2]%frames.length], unit*af[1], unit*af[0]+offset, unit, unit);
  }
}
 
//The following function was taken from pattern_master - 
//https://github.com/golanlevin/Pattern_Master/blob/master/pattern_master/F00.pde
 
function function_PennerEaseInOutBack (x) {
 
  var s = 1.70158 * 1.525;
  x /= 0.5;
 
  var y = 0; 
  if (x < 1) {
    y = 1.0/2.0* (x*x*((s+1.0)*x - s));
  } 
  else {
    x -= 2.0;
    y = 1.0/2.0* (x*x*((s+1.0)*x + s) + 2.0);
  } 
  return y;
}

High-Res Gif (1920x1920):

nerual-AnimatedLoop

"a cursory moment" (get it?)

Perhaps it isn't worthy of a title, but I would like to informally dub it "A Cursory Moment."

The Process:

Some sketches:

 

I was going to follow up the idea of the mouse and pop signs by making the mouse "click" on a place and then bounce off it, like the force of the click sent it flying.  I got as far as making it move in the right physics-like snapping motion with the elastic function, but I didn't get the mouse to rotate, so I decided to play with contrast and repetition to make it look more interesting instead.

This was actually where I was going to stop:

I thought it looked like the James Bond opening, so I wanted to rotate the finger to make it a finger gun. I ended up with the rotated lines of circles lining up on accident and with some tweaking, I got all circles to line up. I thought it looked more interesting that way.

I felt like this work definitely turned out a lot better than expected(although I expected very little at that point). I like the way the repetition and the appearance of the hand go with the elastic movement. It's kind of rhythmic.

It definitely lacks a lot of the mathematical complexity that I would have liked to explore. The black and white was partially an aesthetic choice and mostly a practical one. The elastic movement is rather sloppy. Overall it's very simple. Way back in the beginning I had hoped to made something more along the lines of, well, lines, and I was really disappointed I couldn't manage to incorporate that at all. I think that with more time however, I definitely could have expanded on the concept or at least made the final product smoother.

I later went back and tweaked it a bit more:

Code (easing function from p5.func):

// based on p5.func examples - easing3_animation
// the sound is from the example
// I<3DM rld 


var ease = new p5.Ease();
var speed = 0.02;
var t = 0.;
var doclear;
var x, y, tx, ty, x1, y1;
var osc, rev;
var pause = 0;
var pauseTime = 60;
var fc=0;
var padding = 20;
var ex1,ey1,ex2,ey2,ex3,ey3,ex4,ey4;
var left,right;
var phase;
var startx,starty;
var radius, mr;
var debug = false;

function setup()
{
  frameRate(70);
  createCanvas(640, 640);
  
  radius = 120;
  mr = radius*0.1;
  
  ex1 = left;
  ey1 = height/5;
  ex2 = right;
  ey2 = height/3 + height/6;
  ex3 = left;
  ey3 = ey2 + height/;
  ey4 = 700;
  
  left = width/4;
  right = 3*left;
  phase = 0;
  
  background(255);

  startx = left;
  starty = -radius;
  x = startx;
  y = starty;
  tx = startx;
  ty = starty;
}

function draw()
{
  background(0);
  drawTheThing();
  if (debug){
    push();
    background(255);
    stroke(255);
    text(frameCount, 20,20);  
    pop();
  }
  if (frameCount > 30 && frameCount < 100 && (frameCount%4 ==0)){
    var filename = "myZoetrope_" + nf(frameCount,2) + ".png";
    saveCanvas(filename, 'png');
  }
}

function doPhase(){
    switch(phase){
      case 0:
        ty = starty;
        tx = left;
        phase++;
        break;
      case 1:
        ty = ey1;
        tx = left;
        phase++;
        break;
      case 2:
        ty = ey2;
        tx = right;
        phase++;
        break;
      case 3:
        ty = ey3;
        tx = left;
        phase++;
        break;
      case 4:
        ty = ey4;
        tx = right;
        phase=0;
        phase++;
        break;
      default:
        ty += height/6;
        if (tx==left) tx=right;
        else if(tx==right) tx=left;
        else tx = left;
        phase=0;
        redraw();
        break;
    }
}

function drawTheThing(){
  var func = "elasticOut";
  var q = ease[func](t*2.5);

  var diff = frameCount - fc;
  if (diff > pauseTime){
    doPhase();
    t = 0.;
    fc = frameCount;
  }
  else if (phase==0)
    doPhase();

  x1 = map(q, 0., 1., x, tx);
  y1 = map(q, 0., 1., y, ty);

  fill(255);
  stroke(0);
  ellipse(x1, y1, radius,radius);
  ellipse(x1+left, y1, radius,radius);
  ellipse(x1-left, y1, radius,radius);

  drawMouse(width-x1, height-y1, mr);
  drawMouse(width-x1+left, height-y1, mr);
  drawMouse(width-x1-left, height-y1, mr);

  t+=speed;
  if(t>1.) {
    t=1.;
    x = tx;
    y = ty;
  } 
}

function drawMouse(y,x,s) {
  push(); 
  stroke(255);
  noFill();
  strokeWeight(10);
  ellipse(x,y, 9*s,9*s);
  
  stroke(0);
  strokeWeight(5);
  translate(-1.5*s,0,5);
  rect(x,y,4*s,3*s,5);
  rect(x,y-3.5*s,s,3.5*s,5,5,0,0);
  rect(x+s,y-s,s,s,5,5,0,0);
  rect(x+2*s,y-s,s,s,5,5,0,0);
  rect(x+3*s,y-s,s,s,5,5,0,0);
  rect(x-s,y,s,2*s,5);
  pop();
}

}

lass-AnimatedLoop

For my looping gif, I was really interested in doing a pinching/pulling motion, similar to cell division. At first I was very unsure of how to do this, since I had seen it before but didn't know a technical term. Connie Ye told me to look into metaballs, and I learned the process for making them from this coding challenge by Dan Shiffman. My initial plan involved several components, but after I had gotten the metaballs to work initially, many people urged me to keep it simple instead. I ended up only creating the first main component from the sketches below.   

In the end, I'm glad I went with the simple route. It allowed me to have more flexibility with colors, and it resulted in something much cleaner and not so chaotic. In the future I might try to create something more complex with the code that I wrote, since I planned it out to allow for several "groupings" of metaballs. I think it might be very laggy.

Thank you Golan, Connie, and Lauren for helping me out with this project!

debug view: 

BallGroup metaballs;
int numFrames = 30;
int currentFrame = 0;
int colorChoice = 0;
color[] ballColors, backgroundColors;
 
void setup() {
 size(640, 640);
 noStroke();
 
 ballColors = new color[] {
  color(250, 192, 133), color(169, 67, 117), color(50, 70, 171)
 };
 backgroundColors = new color[] {
  color(200, 94, 141), color(250, 200, 113), color(169, 200, 117)
 };
 
 metaballs = new BallGroup();
 metaballs.addBall(new Metaball(width / 2, height - 40, width / 2, -100, 80, 80));
 metaballs.addBall(new Metaball(width / 2, -100, width / 2, -100, 80, 0));
 metaballs.addBall(new Metaball(width / 2 - 50, height - 50, width / 2 - 50, height - 50, 130, 130));
 metaballs.addBall(new Metaball(width / 2 + 50, height - 50, width / 2 + 50, height - 50, 130, 130));
 metaballs.addBall(new Metaball(width / 2, height * 2, width / 2, height - 40, 0, 80));
 
 metaballs.setColors(ballColors[0], ballColors[1]);
}
 
void draw() {
 currentFrame = (currentFrame + 1) % 30;
 if (currentFrame == 0) {
  colorChoice = (colorChoice + 1) % 3;
  metaballs.setColors(ballColors[colorChoice], ballColors[(colorChoice + 1) % 3]);
 }
 float movement = easing(1.0 * currentFrame / numFrames);
 metaballs.fadeCol = (lerpColor(ballColors[(colorChoice + 1) % 3], ballColors[(colorChoice + 2) % 3], movement));
 background(lerpColor(ballColors[(colorChoice + 0) % 3], ballColors[(colorChoice + 1) % 3], movement));
 //background(255, 200, 200); 
 metaballs.update(movement);
 metaballs.show();
}
 
float easing(float x) {
 //this is where i would put an easing function if i had one 
 return x;
}
 
class Metaball {
 PVector position;
 PVector beginning;
 PVector destination;
 float radius;
 float radius1;
 float radius2;
 Metaball(float x1, float y1, float x2, float y2, float r1, float r2) {
  position = new PVector(x1, y1);
  beginning = new PVector(x1, y1);
  destination = new PVector(x2, y2);
  radius = 100;
  radius1 = r1;
  radius2 = r2;
 }
 void show() { //shows the centers (for debugging) 
  noFill();
  stroke(0);
  strokeWeight(1);
  //ellipse(position.x, position.y, radius, radius);
 }
 void update(float movement) {
  position = position.lerp(beginning, destination, movement);
  radius = lerp(radius1, radius2, movement);
 }
}
 
class BallGroup {
 ArrayList < Metaball > arr;
 color startCol;
 color endCol;
 color col;
 color fadeCol;
 
 BallGroup() {
  arr = new ArrayList < Metaball > ();
  col = color(0, 0, 0);
  startCol = color(0, 0, 0);
  endCol = color(0, 0, 0);
 }
 void setColor(color c) {
  col = c;
 }
 void setColors(color c1, color c2) {
  startCol = c1;
  endCol = c2;
 }
 void addBall(Metaball mb) {
  arr.add(mb);
 }
 void show() {//metaball code from Dan Shiffman: https://www.youtube.com/watch?v=ccYLb7cLB1I
  loadPixels();
  for (int x = 0; x < width; x++) {
   for (int y = 0; y < height; y++) {
    float sum = 0;
    for (Metaball mb: arr) {
     float dst = dist(x, y, mb.position.x, mb.position.y);
     sum += 100 * mb.radius / dst;
    }
    if (sum > 200) {
     //adds a border
     //if (sum < 220)
     //  pixels[x + y * width] = col; 
     //else
     pixels[x + y * width] = lerpColor(col, fadeCol, y * 1.0 / height);
    }
   }
  }
  updatePixels();
  for (Metaball mb: arr)
   mb.show();
 }
 void update(float movement) {
  for (Metaball mb: arr)
   mb.update(movement);
  col = lerpColor(startCol, endCol, movement);
 }
}

paukparl-AnimatedLoop

thoughts:
I wanted to make a series of simple animations with circles as a main theme. But ended up only making two. I wanted to try the blur effect too, but spent too much time figuring out how to export frames from p5 without randomly skipping any frames. But I just gave up. I think slowing down the frame rate might be a solution, although I haven't tried it. Apart from that, I had fun.

gif:

 

 

...
void renderMyDesign (float percent) {
  float x1 = cos(percent*6*PI);
  float x2 = -cos(percent*6*PI);
  float z1 = sin(percent*6*PI);
  float z2 = -sin(percent*6*PI);
  noStroke();
  background(0, 0, 200);
 
 
  translate(width/2, height/2);
  rotate(2*PI*percent);
  if (percent%(1/3f) &lt; 1/6f) {
    fill(255);
    ellipse(
      map(x1, -1, 1, -160, 160), 
      0, 
      200+ map(z1, -1, 1, -80, 80), 
      200+ map(z1, -1, 1, -80, 80));
    fill(0);
    ellipse(
      map(x2, -1, 1, -160, 160), 
      0, 
      200+ map(z2, -1, 1, -80, 80), 
      200+ map(z2, -1, 1, -80, 80));
  } else {
    fill(0);
    ellipse(
      map(x2, -1, 1, -160, 160), 
      0, 
      200+ map(z2, -1, 1, -80, 80), 
      200+ map(z2, -1, 1, -80, 80));
    fill(255);
    ellipse(
      map(x1, -1, 1, -160, 160), 
      0, 
      200+ map(z1, -1, 1, -80, 80), 
      200+ map(z1, -1, 1, -80, 80));
  }
}
...
var frame = 0;
var phase1 = 30;
var phase2 = 50;
var phase3 = 150; // start falling
var phase4 = 160;
var phase5 = 190;
 
var SIZE = 120;
 
var r = 255;
var g = 255;
var b = 255;
 
var cnv;
 
function setup() {
  cnv = createCanvas(640/2, 640/2);
  noStroke();
}
 
function draw() {
  background(225, 0, 100);
 
 
  if (frame &lt;= phase2) { translate(0, map(pennerEaseOutCubic(frame/phase2), 0, 1, -60/2, 520/2)); } // horizontal oscillation if (frame &gt; phase1 &amp;&amp; frame &lt; phase4) { var osc = map(sin((frame-phase2)/PI*2), -1, 1, -50/2, 50/2); var oscScale = reverseSineEase(map(frame, phase1, phase4, 0, 2*PI)); translate(osc*oscScale, 0); // ellipse(width/2, 0, reverseSineEase(map(frame, phase1, phase4, SIZE, 400))); } if (frame &gt; phase2 &amp;&amp; frame &lt;= phase3) { translate(0, map(pennerEaseInOutQuad((frame-phase2)/(phase3-phase2)), 0, 1, 520/2, 200/2)); SIZE += 1.5/2; } if (frame &gt; phase3) {
    translate(0, map(pennerEaseInCubic((frame-phase3)/(phase5-phase3)), 0, 1, 200/2, 840/2)); 
  }
 
  if (frame &gt; phase2 + 20 &amp;&amp; frame &lt; phase4) { r -= 3.5; g -= 3.5; b -= 3.5; } fill(r, g, b); ellipse(width/2, 0, SIZE); frame++; if (frame&gt;phase5) {
    frame=0;
    SIZE = 120;
    r=255;
    g=255;
    b=255;
  }
}
 
function pennerEaseOutCubic(t) {
  t = t-1.0;
  return (t*t*t + 1);
}
 
function pennerEaseInCubic(t) {
  return t*t*t;
}
 
function reverseSineEase(t) {
  return ((1-cos(t))/2)*((1-cos(t))/2);
}
 
function pennerEaseInOutQuad (t) {
  if ((t/=1/2) &lt; 1) return 1/2*t*t;
  return -1/2 * ((--t)*(t-2) - 1);
}

dinkolas-AnimatedLoop

About

I wrote a function with a bunch of parameters that makes a character, and then called the function several times to populate the GIF. I used DoubleExponentialSigmoid, CircularEaseInOut, AdjustableCenterEllipticWindow, and ExponentialSmoothedStaircase to change the pace of the legs and bodies of some characters. I think that the individual characters look good, but I could certainly improve the over all color selection and composition of the piece if I had more time. Now that the character building function is working, a better application of it might be some sort of random walk-cycle generator, so I might implement that at some point. Maybe this concept doesn't fit the GIF format very well, and would be better if the viewer could see more of the possible walks.

Sketches

Code

I used Golan's Java Processing Template, here's my code:

// 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.3.6
// Prof. Golan Levin, January 2018
 
//===================================================
// Global variables. 
String  myNickname = "dinkolas"; 
int     nFramesInLoop = 120;
int     nElapsedFrames;
boolean bRecording; 
 
//===================================================
void setup() {
  size (640,640); 
  bRecording = true;
  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 &gt;= nFramesInLoop) {
      bRecording = false;
    }
  }
}
 
//===================================================
void renderMyDesign (float percent) {
  color c1 = color(59,58,133);
  color c2 = color(137,52,109);
  color c3 = color(199,96,88);
  color c4 = color(255,178,72);
  color c5 = color(255,225,150);
  background(c5);
 
  //1
  color bodyCol = c3;
  color legCol =  c2;
  float originX = 100;
  float originY = 200;
  float xShift = -20;
  float altitude = 100;
  float diameter = 70;
  float bounce = 10;
  float legLength = 50;
  float legWeight = 10;
  float footLength = 10;
  float cycles = 2;
  float footH = 40;
  float footV = 20;
  float phase = 0;
  float legT = function_DoubleExponentialSigmoid(percent,.5);
  float bodyT = function_DoubleExponentialSigmoid(percent,.5);
  drawCharacter(bodyCol, legCol, originX, originY, xShift, altitude, diameter, bounce, legLength, legWeight, footLength, cycles, footH, footV, phase, legT, bodyT);
 
  //2
  bodyCol = c1;
  legCol = c2;
  originX = 320;
  originY = 190;
  xShift = 10;
  altitude = 90;
  diameter = 120;
  bounce = 5;
  legLength = 25;
  legWeight = 15;
  footLength = 10;
  cycles = 1;
  footH = 20;
  footV = 10;
  phase = .5;
  legT = function_ExponentialSmoothedStaircase(percent,.05,4);
  bodyT = legT;
  drawCharacter(bodyCol, legCol, originX, originY, xShift, altitude, diameter, bounce, legLength, legWeight, footLength, cycles, footH, footV, phase, legT, bodyT);
 
  //3
  bodyCol = c4;
  legCol = c3;
  originX = 520;
  originY = 250;
  xShift = 0;
  altitude = 200;
  diameter = 50;
  bounce = 15;
  legLength = 100;
  legWeight = 8;
  footLength = 10;
  cycles = 3;
  footH = 30;
  footV = 60;
  phase = .6;
  legT = 1-percent;
  bodyT = 1-percent;
  drawCharacter(bodyCol, legCol, originX, originY, xShift, altitude, diameter, bounce, legLength, legWeight, footLength, cycles, footH, footV, phase, legT, bodyT);
 
  //4
  for (int i = 0; i &lt; 3; i++){
    bodyCol = c3;
    legCol = c1;
    originX = 50+55*i;
    originY = 350;
    xShift = 0;
    altitude = 40;
    diameter = 40;
    bounce = 3;
    legLength = 20;
    legWeight = 4;
    footLength = 3;
    cycles = 2;
    footH = 10;
    footV = 6;
    phase = .1+.3*i;
    legT = function_AdjustableCenterEllipticWindow (percent, .5);
    bodyT = percent;
    drawCharacter(bodyCol, legCol, originX, originY, xShift, altitude, diameter, bounce, legLength, legWeight, footLength, cycles, footH, footV, phase, legT, bodyT);
  }
 
  //5
  bodyCol = c3;
  legCol = c1;
  originX = 490;
  originY = 430;
  xShift = 10;
  altitude = 90;
  diameter = 50;
  bounce = 15;
  legLength = 60;
  legWeight = 8;
  footLength = 10;
  cycles = 1;
  footH = 80;
  footV = 130;
  phase = .6;
  legT = percent;
  bodyT = percent;
  drawCharacter(bodyCol, legCol, originX, originY, xShift, altitude, diameter, bounce, legLength, legWeight, footLength, cycles, footH, footV, phase, legT, bodyT);
 
  //6
  bodyCol = c3;
  legCol = c4;
  originX = 300;
  originY = 380;
  xShift = -30;
  altitude = 100;
  diameter = 60;
  bounce = 25;
  legLength = 40;
  legWeight = 10;
  footLength = 10;
  cycles = 2;
  footH = 40;
  footV = 40;
  phase = .8;
  legT = 2*function_CircularEaseInOut(percent);
  bodyT = percent;
  drawCharacter(bodyCol, legCol, originX, originY, xShift, altitude, diameter, bounce, legLength, legWeight, footLength, cycles, footH, footV, phase, legT, bodyT);
 
  //7
  bodyCol = c1;
  legCol = c4;
  originX = 400;
  originY = 600;
  xShift = 0;
  altitude = 70;
  diameter = 30;
  bounce = 5;
  legLength = 60;
  legWeight = 10;
  footLength = 5;
  cycles = 1;
  footH = 120;
  footV = 35;
  phase = 0;
  legT = percent;
  bodyT = 1-percent;
  drawCharacter(bodyCol, legCol, originX, originY, xShift, altitude, diameter, bounce, legLength, legWeight, footLength, cycles, footH, footV, phase, legT, bodyT);
 
  //8
  bodyCol = c2;
  legCol = c4;
  originX = 100;
  originY = 580;
  xShift = 20;
  altitude = 130;
  diameter = 70;
  bounce = 40;
  legLength = 70;
  legWeight = 20;
  footLength = 30;
  cycles = 2;
  footH = 30;
  footV = 35;
  phase = .5;
  legT = percent;
  bodyT = 1-percent;
  drawCharacter(bodyCol, legCol, originX, originY, xShift, altitude, diameter, bounce, legLength, legWeight, footLength, cycles, footH, footV, phase, legT, bodyT);
}
 
void drawCharacter(color bodyCol, color legCol, float originX, float originY, float xShift, float altitude, float diameter, float bounce, float legLength, float legWeight, float footLength, float cycles, float footH, float footV, float phase, float legT, float bodyT) {
  //set origin and coordinate system so up is positive
  translate(originX,originY);
  scale(1,-1);
  legT = ((legT%(1/cycles))*cycles+phase)%1;
  bodyT = ((bodyT%(1/cycles/2))*2*cycles+phase)%1;
  stroke(bodyCol);
  strokeWeight(legWeight);
  line(-footH-20,-legWeight/2,footH+20+footLength,-legWeight/2); 
 
  //body positions
  float bodyX = xShift+map(-sin(TWO_PI*bodyT), -1,1, -bounce/5, +bounce/5);
  float bodyY = map(-cos(TWO_PI*bodyT), -1,1, altitude-bounce, altitude+bounce);
 
  //back leg
  //hip=(x1,y1), ankle=(x2,y2), knee = (a,b), toe = (x3,y3)
  translate(0,legWeight/2);
  strokeWeight(legWeight);
  stroke(legCol);
  noFill();
  float x1 = bodyX;
  float y1 = bodyY-diameter/3;
  float x2 = map(-cos(TWO_PI*legT), -1,1,-footH,footH);
  float y2 = max(0,map(sin(TWO_PI*legT), -1, 1, -footV, footV));
  float mult = -.5*sqrt(4*pow(legLength,2)/(pow(x2-x1,2)+pow(y2-y1,2))-1);
  float a = .5*(x1+x2)+mult*(y2-y1);
  float b = .5*(y1+y2)+mult*(x1-x2);
  float x3 = x2 + footLength; 
  float y3 = 0;
  if (2*legLength &lt; sqrt(pow(x2-x1,2)+pow(y2-y1,2))){line(x1,y1,x2,y2);}
  else{line(x1,y1,a,b);line(a,b,x2,y2);}
  if (y2==0) {line(x2,y2,x3,y3);}
  else {
    float angle = atan((y2-y1)/(x2-x1));
    angle = PI/2-angle;
    if (x2&lt;x1) {angle+=PI;}
    translate(x2,y2);
    rotate(-angle);
    line(0,0,-footLength,0);
    rotate(angle);
    translate(-x2,-y2);
  }
  translate(0,-legWeight/2);
 
  //body
  noStroke();
  fill(bodyCol);
  ellipse(bodyX,bodyY,diameter,diameter);
 
  //front leg
  //hip=(x1,y1), ankle=(x2,y2), knee = (a,b), toe = (x3,y3)
  legT = (legT+.5)%1;
  translate(0,legWeight/2);
  strokeWeight(legWeight);
  stroke(legCol);
  x1 = bodyX;
  y1 = bodyY-diameter/3;
  x2 = map(-cos(TWO_PI*legT), -1,1,-footH,footH);
  y2 = max(0,map(sin(TWO_PI*legT), -1, 1, -footV, footV));
  mult = -.5*sqrt(4*pow(legLength,2)/(pow(x2-x1,2)+pow(y2-y1,2))-1);
  a = .5*(x1+x2)+mult*(y2-y1);
  b = .5*(y1+y2)+mult*(x1-x2);
  x3 = x2 + footLength; 
  y3 = 0;
  if (2*legLength &lt; sqrt(pow(x2-x1,2)+pow(y2-y1,2))){line(x1,y1,x2,y2);}
  else{line(x1,y1,a,b);line(a,b,x2,y2);}
  if (y2==0) {line(x2,y2,x3,y3);}
  else {
    float angle = atan((y2-y1)/(x2-x1));
    angle = PI/2-angle;
    if (x2&lt;x1) {angle+=PI;}
    translate(x2,y2);
    rotate(-angle);
    line(0,0,-footLength,0);
    rotate(angle);
    translate(-x2,-y2);
  }
  translate(0,-legWeight/2);
 
  //reset origin
  scale(1,-1);
  translate(-originX,-originY);
}
 
 
 
//===================================================
// Taken from https://github.com/golanlevin/Pattern_Master
float function_DoubleExponentialSigmoid (float x, float a) {
  // functionName = "Double-Exponential Sigmoid";
 
  float min_param_a = 0.0 + EPSILON;
  float max_param_a = 1.0 - EPSILON;
  a = constrain(a, min_param_a, max_param_a); 
  a = 1-a;
 
  float y = 0;
  if (x&lt;=0.5) {
    y = (pow(2.0*x, 1.0/a))/2.0;
  } else {
    y = 1.0 - (pow(2.0*(1.0-x), 1.0/a))/2.0;
  }
  return y;
}
 
float function_ExponentialSmoothedStaircase (float x, float a, int n) {
  //functionName = "Smoothed Exponential Staircase";
  // See http://web.mit.edu/fnl/volume/204/winston.html
 
  float fa = sq (map(a, 0,1, 5,30));
  float y = 0; 
  for (int i=0; i&lt;n; i++){
    y += (1.0/(n-1.0))/ (1.0 + exp(fa*(((i+1.0)/n) - x)));
  }
  y = constrain(y, 0,1); 
  return y;
}
 
float function_AdjustableCenterEllipticWindow (float x, float a){
  //functionName = "Adjustable-Center Elliptic Window";
 
  float min_param_a = 0.0 + EPSILON;
  float max_param_a = 1.0 - EPSILON;
  a = constrain(a, min_param_a, max_param_a);
 
  float y = 0;
 
  if (x&lt;=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;
}
 
float function_CircularEaseInOut (float x) {
  //functionName = "Penner's Circular Ease InOut";
 
  float y = 0; 
  x *= 2.0; 
 
  if (x &lt; 1) {
    y =  -0.5 * (sqrt(1.0 - x*x) - 1.0);
  } else {
    x -= 2.0;
    y =   0.5 * (sqrt(1.0 - x*x) + 1.0);
  }
 
  return y;
}

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

nannon-AnimatedLoop

I originally wanted to do something totally different (a set of eyes that opened into new eyes), but I eventually thought that it was too complicated and illustrative and decided to invest my time in doing something 3D instead. I saw this cool gif with rotating text in a cylinder in the spirit of Zach Lieberman the other day, and wanted to do something similar. Unfortunately, I decided all of the Wednesday night, and only had Thursday to do it. Having never worked in 3D before, this was really torturous, but still kind of rewarding. I'm disappointed I didn't get the final (as of now) to how I imagined it in my head, but I'm going to keep working on this. Ultimately, I want the text to actually be a coherent sentence, and have different text on the inside of the cylinder and out. A lot of time was spent figuring out Geomerative and cyinder geometry, but I did really learn a lot. I didn't have time to use p5_func, but hopefully will get around to it as I continue to flesh this guy out.

Thank you Golan  for taking the time to explain a ton mathematics.

// 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.3.6
// Prof. Golan Levin, January 2018
import geomerative.*;
 
RFont f;
RShape fire;
RPoint[] points;
RShape[] letterShapes;
//===================================================
// Global variables. 
String  myNickname = "nannon"; 
int     nFramesInLoop = 120;
int     nElapsedFrames;
boolean bRecording; 
 
//===================================================
void setup() {
  size (640, 640, P3D); 
  lights();
  background(0);
  bRecording = false;
  nElapsedFrames = 0;
 
  RG.init(this);
  fire = RG.getText("x", "Optimo.ttf", 72, CENTER);
 
  RG.setPolygonizer(RG.UNIFORMLENGTH);
  RG.setPolygonizerLength(1);
  fill(255);
 
  letterShapes = fire.children;
 
 
  smooth();
}
//===================================================
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 &gt;= nFramesInLoop) {
      bRecording = false;
    }
  }
}
 
//===================================================
void renderMyDesign (float percent) {
  //
  background(0);
  translate(320, 320, 200);
  rotateX(PI);
  rotateY(map(percent,0,1,0,PI/2));
  rotateZ(sin(PI));
  stroke(255);
  noFill(); 
 
 
  int r= 100;
  int nSlices = 50;
  int zA = -20;
  int zB = 20;
 
 
 
 
 
  rotateY(PI/2);
  rotateX(PI/2);
  noFill();
 
  // draw a cylinder
  noFill(); 
  stroke(255); 
  beginShape(TRIANGLE_STRIP);
  for (int i=0; i&lt;=nSlices; i++) {
    float theta = map(i, 0, nSlices, 0, 2*PI);
    float pX = r*cos(theta);
    float pY = r*sin(theta);
    vertex(pX, pY, zA);
    vertex(pX, pY, zB);
  }
  endShape();
 
  // print("x1:"+points[0].x+" "+"y:"+ points[0].y+"    ");
  // print("x2:"+points[points.length-1].x+" "+"y:"+ points[points.length-1].y+"    ");
 
 
 
   //beginShape();
   //for (int i=0; i&lt;points.length; i++) {
   //  float newX = map(points[i].x, -187, 189, 0, 0.6*(2*PI));
   //  float newZ = map(points[i].y, -24, 0, 0, 0.6*(2*PI));
   //  vertex(r*cos(newX), points[i].y, abs(r*cos(newZ)));
 
   //  endShape();
   //}
 
 
 
  noFill(); 
  stroke(255); 
  //int nLetters = 16; 
  //for (int i=0; i&lt;nLetters; i++) {
 
 
 
 
    //int nPointsInThisContour = 16;
    for (int k=0; k&lt;10; k++) {
      pushMatrix();
      float letterTheta = k* radians(45.0);
 
 
      float letterX = r * cos(letterTheta);
      float letterY = r * sin(letterTheta); 
 
 
      //float eased = function_DoubleExponentialSigmoid(percent, 0.7);
      //float newletterY = map(eased, 0,1,0,PI);
 
      translate(letterX, letterY);
      rotateX( radians(90)); 
      rotateY(letterTheta+ radians(90));
 
      fill(255);
      stroke(255);
      beginShape();
      RPoint[] points = letterShapes[0].getPoints();
      for (int j=0; j&lt;points.length; j++) {
 
        //print(points[j].x);
        //float ht = map(j, 0, nPointsInThisContour, 0, TWO_PI); 
        float hpx = points[j].x; 
        float hpy =  points[j].y; 
        float hpz =0;
 
 
        vertex(hpx, hpy, hpz);
 
        //ellipse(hpx,hpy,10,10);
      }
      endShape();
      popMatrix();
    }
 
}
float function_DoubleExponentialSigmoid (float x, float a) {
  // functionName = "Double-Exponential Sigmoid";
 
  float min_param_a = 0.0 + EPSILON;
  float max_param_a = 1.0 - EPSILON;
  a = constrain(a, min_param_a, max_param_a); 
  a = 1-a;
 
  float y = 0;
  if (x&lt;=0.5) {
    y = (pow(2.0*x, 1.0/a))/2.0;
  } else {
    y = 1.0 - (pow(2.0*(1.0-x), 1.0/a))/2.0;
  }
  return y;
}

casher-AnimatedLoop

https://editor.p5js.org/cassiescheirer/sketches/SknMGG_O7

I don't know why but my computer was having an incredibly hard time exporting my frames -- some were being skipped, and everything was patchy. I had Char help me export and the GIF still ended up being way too slow. Click the link above it or here (https://editor.p5js.org/cassiescheirer/sketches/SknMGG_O7) to see it in the p5.js editor. Sorry about this.

Anyway, I am mostly happy with this project. I wish I had had more time to make it more fancy, and a better laptop so that I wouldn't have gotten so frustrated at the lag, but overall I am proud. It took a little while to come up with the idea, but once I knew what I wanted to do, I had to make it happen. Again I had Char talk through the syntax I didn't know, but a lot of it I figured out myself this time. For the easing function, I knew that I was probably going to need one to simulate the "bouncing" of the ball, so I looked for one that matched that motion the most. I went through a couple of them before concluding that the doubleExponentialSigmoid, which Golan had already used in the template, was actually the best one. Having that as a guide was helpful.

I'm proud of myself for having a set idea in mind and then putting in the effort to execute it, even though it was hard. I also really like the simplicity of my GIF. At one point I was about to add some flashing circles in the background, but Char told me that the minimalist-ness of it was what made it so nice. However, I think it is only that way because I hadn't allotted enough time to make it super grand. If I had more time and had known more of the p5.js syntax, I definitely would have made it more complicated.  As you can see in my sketch (bottom left), I started an idea that branched off of the flipping square -- a skillet flipping a pancake. I stopped myself there though, as I knew I probably didn't know enough to do that. I hope I can come back to this project when I have time and make the pancake idea.

 

//===================================================
// User-modifiable global variables. 
var myNickname = "Cassie";
var nFramesInLoop = 120;
var bEnableExport = true;
 
// Other global variables you don't need to touch.
var nElapsedFrames;
var bRecording;
var theCanvas;
 
var canvaswidth = 645;
var canvasheight = 645;
 
var flipper = false;
 
//===================================================
function setup() {
  theCanvas = createCanvas(canvaswidth, canvasheight);
  bRecording = false;
  nElapsedFrames = 0;
  rectMode(CENTER);
}
 
//===================================================
function keyTyped() {
  if (bEnableExport) {
    if ((key === 'f') || (key === 'F')) {
      bRecording = true;
      nElapsedFrames = 0;
    }
  }
}
 
//===================================================
function draw() {
 
  // Compute a percentage (0...1) representing where we are in the loop.
  var percentCompleteFraction = 0;
  if (bRecording) {
    percentCompleteFraction = float(nElapsedFrames) / float(nFramesInLoop);
  } else {
    percentCompleteFraction = float(frameCount % nFramesInLoop) / float(nFramesInLoop);
  }
 
  // Render the design, based on that percentage. 
  // This function renderMyDesign() is the one for you to change. 
  renderMyDesign (percentCompleteFraction);
 
  // If we're recording the output, save the frame to a file. 
  // Note that the output images may be 2x large if you have a Retina mac. 
  // You can compile these frames into an animated GIF using a tool like: 
  if (bRecording &amp;&amp; bEnableExport) {
    var frameOutputFilename = myNickname + "_frame_" + nf(nElapsedFrames, 4) + ".png";
    print("Saving output image: " + frameOutputFilename);
    saveCanvas(theCanvas, frameOutputFilename, 'png');
    nElapsedFrames++;
 
    if (nElapsedFrames &gt;= nFramesInLoop) {
      bRecording = false;
    }
  }
}
 
//===================================================
function renderMyDesign (percent) {
  //
  // THIS IS WHERE YOUR ART GOES. 
  // 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. 
  // Use, modify, or delete whatever you prefer from this example. 
  // This example uses several different graphical techniques. 
  // Remember to SKETCH FIRST!
 
  //----------------------
  // here, I set the background and some other graphical properties
  var x = abs(cos(percent*TWO_PI))
  var y = doubleExponentialSigmoid(x, 0.25);
  var squish =  map(y, 0, 1, 10, 30);
 
  background(0);
  rectMode(CENTER);
  strokeWeight(2);
  stroke(255);
 
  // flipping base
  push();
  translate(width / 2 + 20*sin(TWO_PI*percent), height-250);
  rotate(sin(TWO_PI*percent));
  var flipx = map(-cos(frameCount/9.5), -1, 1, 0, 200);
 
  if (frameCount % 120 &gt;= 60) {
		fill(200, 100, 200);
  }
  else {
    fill(200,200,100);
  }
  rect(0, 20, flipx, 200);
  pop();
 
  translate(width / 2, height / 2);
 
  // ball shadow
  push();
  var shadowbounce = map(y, 0, 1, 100, 40);
  fill(0);
  noStroke();
  rotate(-abs(cos(TWO_PI*percent)));
  ellipse(0, shadowbounce, cos(frameCount/20)*20, cos(frameCount/20)*15);
  pop();
 
  // ball
  push();
  fill(255);
	var ballbounce = map(-y,0,1,100,360);
  distance = height - ballbounce;
  ellipse(0, ballbounce, 30, squish);
  pop();
 
// Symmetric double-element sigmoid function ('_a' is the slope)
// See https://github.com/IDMNYU/p5.js-func/blob/master/lib/p5.func.js
// From: https://idmnyu.github.io/p5.js-func/
//===================================================
function doubleExponentialSigmoid (_x, _a){
  if(!_a) _a = 0.75; // default
 
  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);
  _a = 1-_a;
 
  var _y = 0;
  if (_x&lt;=0.5){
    _y = (pow(2.0*_x, 1.0/_a))/2.0;
  }
  else {
    _y = 1.0 - (pow(2.0*(1.0-_x), 1.0/_a))/2.0;
  }
  return(_y);
	}
}

yalbert-AnimatedLoop

Although this pretty much turned out as an elaborate loading bar, I enjoyed making this piece. I'm partial to animation and using an easing technique was a good way to hone that skill. One part of this that I struggled with was color balance. It took me a while to experiment with black on white, white on black, and other colors until I finally settled on this combination. An element that I didn't fully resolve was the relationship between the spiral and the canvas. Making it too small made it feel like it was floating in space, but when it was too big it fit oddly in it.

// This is a template for creating a looping animation in p5.js (JavaScript). 
// When you press the 'F' key, this program will export a series of images into
// your default Downloads folder. These can then be made into an animated gif. 
// This code is known to work with p5.js version 0.6.0
// Prof. Golan Levin, 28 January 2018
 
// INSTRUCTIONS FOR EXPORTING FRAMES (from which to make a GIF): 
// 1. Run a local server, using instructions from here:
//    https://github.com/processing/p5.js/wiki/Local-server
// 2. Set the bEnableExport variable to true.
// 3. Set the myNickname variable to your name.
// 4. Run the program from Chrome, press 'f'. 
//    Look in your 'Downloads' folder for the generated frames.
// 5. Note: Retina screens may export frames at twice the resolution.
 
 
//===================================================
// User-modifiable global variables. 
var myNickname = "nickname";
var nFramesInLoop = 120;
var bEnableExport = true;
var dots = [];
var radius = 1200;
var dotSize = 45;
var worms = [];
var numWorms = 5
var colors = [[255, 0, 0], [0, 255, 0], [0, 0, 255]]
var curColor = [255, 0, 0]
var curColorInd = 0;
 
// Other global variables you don't need to touch.
var nElapsedFrames;
var bRecording;
var theCanvas;
 
//===================================================
function setup() {
  theCanvas = createCanvas(640, 640);
  bRecording = false;
  nElapsedFrames = 0;
  angleMode(DEGREES)
 
  for(j = 0; j &lt;= numWorms; j++){ var r = lerp(243, 0, (j)/(numWorms)); var g = lerp(206, 0, (j)/(numWorms)); var b = lerp(80, 0, (j)/(numWorms)); var color = [r, g, b] var worm = new Worm(color) worm.setup() worms.push(worm) } } //=================================================== function keyTyped() { if (bEnableExport) { if ((key === 'f') || (key === 'F')) { bRecording = true; nElapsedFrames = 0; } } } //=================================================== function draw() { // Compute a percentage (0...1) representing where we are in the loop. var percentCompleteFraction = 0; if (bRecording) { percentCompleteFraction = float(nElapsedFrames) / float(nFramesInLoop); } else { percentCompleteFraction = float(frameCount % nFramesInLoop) / float(nFramesInLoop); } // Render the design, based on that percentage. // This function renderMyDesign() is the one for you to change. renderMyDesign(percentCompleteFraction); // If we're recording the output, save the frame to a file. // Note that the output images may be 2x large if you have a Retina mac. // You can compile these frames into an animated GIF using a tool like: if (bRecording &amp;&amp; bEnableExport) { var frameOutputFilename = myNickname + "_frame_" + nf(nElapsedFrames, 4) + ".png"; print("Saving output image: " + frameOutputFilename); saveCanvas(theCanvas, frameOutputFilename, 'png'); nElapsedFrames++; if (nElapsedFrames &gt;= nFramesInLoop) {
      bRecording = false;
    }
  }
}
 
//===================================================
function renderMyDesign (percent) {
  //
  // THIS IS WHERE YOUR ART GOES. 
  // 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. 
  // Use, modify, or delete whatever you prefer from this example. 
  // This example uses several different graphical techniques. 
  // Remember to SKETCH FIRST!
 
  //----------------------
 
 
 
 
  // here, I set the background and some other graphical properties
  background(0);
  smooth();
  stroke(0, 0, 0);
  strokeWeight(2);
 
 
  // if(percent == 0.75){
  //   curColorInd +=1;
  //   curColorInd = curColorInd % colors.length;
  //   curColor = colors[curColorInd];
  // }
 
  print(percent)
 
  for(j = 0; j &lt; worms.length; j++){
    var r = lerp(curColor[0], 0, (j)/(numWorms));
    var g = lerp(curColor[1], 0, (j)/(numWorms));
    var b = lerp(curColor[2], 0, (j)/(numWorms));
    var color = [r, g, b];
 
    var worm = worms[j];
    worm.col = color
    worm.update((percent + .04 *j)%1, j);
  }
 
 
}
 
function Worm(c){
  this.dots = []
  this.col = c
  this.setup = function(){
    for(i = 0; i&lt; 60; i++){
      dots.push(new Dot(i * .003))
    }
  }
  this.update = function(percent, sizeOff){
    for(i = 0; i &lt; dots.length; i++){
      d = dots[i];
      d.calculatePos(percent);
      d.draw(this.col, sizeOff);
    }
  }
}
 
function Dot(off){
  this.offset = off
  this.x = 0
  this.y = 0
  this.radius = map(this.offset, 0, 1, 0, radius)
  this.angle = 0
  this.eased = 0
 
  this.calculatePos = function(percent){
    this.eased = quadraticInOut((percent + this.offset)%1);
    this.eased = (this.eased - 0.25)%1.0; // shifted by a half-loop, for fun
    this.angle = map(this.eased, 0, 1, 0, 360); 
 
    this.x = cos(this.angle)*this.radius + width/2;
    this.y = sin(this.angle)*this.radius + height/2;
  }
 
  this.draw = function(color, sizeOff){
    fill(color);
    noStroke();
    ellipse(this.x, this.y, dotSize + sizeOff*2, dotSize + sizeOff*2);
  }
}
 
 
// symmetric double-element sigmoid function (a is slope)
// See https://github.com/IDMNYU/p5.js-func/blob/master/lib/p5.func.js
// From: https://idmnyu.github.io/p5.js-func/
//===================================================
 
function gompertz(_x, _a){
    if(!_a) _a = 0.25; // default
    var min_param_a = 0.0 + Number.EPSILON;
    _a = max(_a, min_param_a);
 
    var b = -8.0;
    var c = 0 - _a*16.0;
    var _y = exp( b * exp(c * _x));
 
    var maxVal = exp(b * exp(c));
    var minVal = exp(b);
    _y = map(_y, minVal, maxVal, 0, 1);
 
    return(_y);
}
 
function quadraticInOut(_x) {
    if(_x &lt; 0.5)
    {
      return(8 * _x * _x * _x * _x);
    }
    else
    {
      var _v = (_x - 1);
      return(-8 * _v * _v * _v * _v + 1);
    }
}
function sineOut(_x) {
    return(sin(_x * HALF_PI));
  }
 
function cubicIn(_x) {
    return(_x * _x * _x);
}

rigatoni-AnimatedLoop

This project felt like jumping into the deep end as far as rendering goes. I was inspired by Dan Shiffman's 4D Tesseract challenge, although I wanted to make the gif my own. Until now I hadn't really given any thought to there being dimensions beyond the ones we can comfortably perceive and how they work, and thinking about higher dimensions led me down a rabbit hole of awesome math and logic content by channels like Numberphile and 3B1B. Dan Shiffman's challenge in particular was in Java and was a lot more conducive to 3d with its P3D renderer, but I think I found a suitable workaround by drawing quads instead of individual points using WEBGL. I was also treating the easing function (doubleExpSig) as an after thought, but once I actually used it to control what I see as distance along the 4th dimension I was surprised by what a huge role it played in the aesthetic of the loop. I can't imagine pulling off a smooth and natural motion without it. That being said however, the gif doesn't convincingly feel "4D" and I was to revisit it once I have more time.

I didn't end up sketching very much for this assignment, but here's a few things I did that helped me keep track of what I was doing

I realized pretty early into the project that hardcoding in each of the 16 4d vertices was time-consuming and I often drew the quads in the wrong order. I decided to make use of modulus and int division to set the points in the right order.

This is me marking the points needed to make up each face. This is maybe a quarter of what I ended up needing and I think I could have spent more time on this planning phase.

// This is a template for creating a looping animation in p5.js (JavaScript). 
// When you press the 'F' key, this program will export a series of images into
// your default Downloads folder. These can then be made into an animated gif. 
// This code is known to work with p5.js version 0.6.0
// Prof. Golan Levin, 28 January 2018
 
// INSTRUCTIONS FOR EXPORTING FRAMES (from which to make a GIF): 
// 1. Run a local server, using instructions from here:
//    https://github.com/processing/p5.js/wiki/Local-server
// 2. Set the bEnableExport variable to true.
// 3. Set the myNickname variable to your name.
// 4. Run the program from Chrome, press 'f'. 
//    Look in your 'Downloads' folder for the generated frames.
// 5. Note: Retina screens may export frames at twice the resolution.
 
 
//===================================================
// User-modifiable global variables. 
var myNickname = "rigatoni";
var nFramesInLoop = 60;
var bEnableExport = true;
 
// Other global variables you don't need to touch.
var nElapsedFrames;
var bRecording;
var theCanvas;
 
//===================================================
function setup() {
  theCanvas = createCanvas(720, 720, WEBGL);
  bRecording = false;
  nElapsedFrames = 0;
}
 
//===================================================
function keyTyped() {
  if (bEnableExport) {
    if ((key === 'f') || (key === 'F')) {
      bRecording = true;
      nElapsedFrames = 0;
    }
  }
}
 
//===================================================
function draw() {
 
  // Compute a percentage (0...1) representing where we are in the loop.
  var percentCompleteFraction = 0;
  if (bRecording) {
    percentCompleteFraction = float(nElapsedFrames) / float(nFramesInLoop);
  } else {
    percentCompleteFraction = float(frameCount % nFramesInLoop) / float(nFramesInLoop);
  }
 
  // Render the design, based on that percentage. 
  // This function renderMyDesign() is the one for you to change. 
  renderMyDesign (percentCompleteFraction);
 
  // If we're recording the output, save the frame to a file. 
  // Note that the output images may be 2x large if you have a Retina mac. 
  // You can compile these frames into an animated GIF using a tool like: 
  if (bRecording && bEnableExport) {
    var frameOutputFilename = myNickname + "_frame_" + nf(nElapsedFrames, 4) + ".png";
    print("Saving output image: " + frameOutputFilename);
    saveCanvas(theCanvas, frameOutputFilename, 'png');
    nElapsedFrames++;
 
    if (nElapsedFrames >= nFramesInLoop) {
      bRecording = false;
    }
  }
}
 
//===================================================
function renderMyDesign (percent) {
  background(180);
  var cube = new Hypercube(500, percent)
  rotateY(percent*PI)
  cube.Draw()
}
 
function Hypercube(size, margin) {
	this.points = []
  margin -= 1
  margin = doubleExponentialSigmoid(margin)
	for(var i=0; i<16; i++) {
    var j = i
		var w = floor(j/8)*margin
    j=j%8 
    var stereo = 1/(2-w)
    var z = floor(j/4)*stereo-(0.5*stereo)
    j=j%4
    var y = floor(j/2)*stereo-(0.5*stereo)
    j=j%2
    var x = floor(j/1)*stereo-(0.5*stereo)
  	this.points[i] = new P4(x*size, y*size, z*size, 0)
  }
  this.Draw = function() {
    fill(225,15)
		var front = new Face(this.points[4], this.points[5], 
                         this.points[6], this.points[7])
		var back = new Face(this.points[0], this.points[1], 
                         this.points[2], this.points[3])
		var left = new Face(this.points[0], this.points[2], 
                         this.points[4], this.points[6]) 
    var right = new Face(this.points[1], this.points[3], 
                     this.points[5], this.points[7]) 
		var top = new Face(this.points[2], this.points[3], 
                         this.points[6], this.points[7]) 
		var bottom = new Face(this.points[0], this.points[1], 
                         this.points[4], this.points[5]) 
		var sFront = new Face(this.points[12], this.points[13], 
                         this.points[14], this.points[15])
		var sBack = new Face(this.points[8], this.points[9], 
                         this.points[10], this.points[11])
		var sLeft = new Face(this.points[8], this.points[10], 
                         this.points[12], this.points[14]) 
    var sRight = new Face(this.points[9], this.points[11], 
                     this.points[13], this.points[15]) 
		var sTop = new Face(this.points[10], this.points[11], 
                         this.points[14], this.points[15]) 
		var sBottom = new Face(this.points[8], this.points[9], 
                         this.points[12], this.points[13]) 
 
    var pfront = new Face(this.points[4], this.points[5], 
                         this.points[12], this.points[13])
		var pback = new Face(this.points[0], this.points[1], 
                         this.points[8], this.points[9])
		var pleft = new Face(this.points[0], this.points[2], 
                         this.points[8], this.points[10]) 
    var pright = new Face(this.points[1], this.points[3], 
                     this.points[9], this.points[11]) 
		var ptop = new Face(this.points[2], this.points[3], 
                         this.points[10], this.points[11]) 
		var pbottom = new Face(this.points[0], this.points[4], 
                         this.points[8], this.points[12]) 
		var psFront = new Face(this.points[1], this.points[5], 
                         this.points[9], this.points[13])
		var psBack = new Face(this.points[5], this.points[7], 
                         this.points[13], this.points[15])
		var psLeft = new Face(this.points[3], this.points[7], 
                         this.points[11], this.points[15]) 
    var psRight = new Face(this.points[2], this.points[6], 
                     this.points[10], this.points[14]) 
		var psTop = new Face(this.points[6], this.points[7], 
                         this.points[14], this.points[15]) 
		var psBottom = new Face(this.points[4], this.points[6], 
                         this.points[12], this.points[14]) 
 
  	front.Draw()
    back.Draw()
    left.Draw()
    right.Draw()
    sFront.Draw()
    sBack.Draw()
    sLeft.Draw()
    sRight.Draw()
  	pfront.Draw()
    pback.Draw()
    pleft.Draw()
    pright.Draw()
    psFront.Draw()
    psBack.Draw()
    psLeft.Draw()
    psRight.Draw() 
  }
}
 
function Face(p1, p2, p3, p4) {
  var distance = 200
  this.p1 = p1
  this.p2 = p2
  this.p3 = p3
  this.p4 = p4
 
  this.Draw = function() {
    beginShape()
    	vertex(this.p1.x,this.p1.y,this.p1.z)
    	vertex(this.p2.x,this.p2.y,this.p2.z)
    	vertex(this.p4.x,this.p4.y,this.p4.z)
    	vertex(this.p3.x,this.p3.y,this.p3.z)
    endShape(CLOSE)
  }
 
  this.Print = function() {
  	 this.p1.Print()
     this.p2.Print()
     this.p3.Print()
     this.p4.Print()
  }
}
 
function P4(x,y,z,w) {
	this.x = x
  this.y = y
  this.z = z
  this.w = w
  this.Print = function() {
		print(this.x, this.y, this.z, this.w) 
  }
  this.ScalarMult = function(multiplier) {
		this.x*=multiplier
    this.y*=multiplier
    this.z*=multiplier
    this.w*=multiplier
  }
}
// Symmetric double-element sigmoid function ('_a' is the slope)
// See https://github.com/IDMNYU/p5.js-func/blob/master/lib/p5.func.js
// From: https://idmnyu.github.io/p5.js-func/
//===================================================
function doubleExponentialSigmoid (_x, _a){
  if(!_a) _a = 0.75; // default
 
  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);
  _a = 1-_a;
 
  var _y = 0;
  if (_x<=0.5){
    _y = (pow(2.0*_x, 1.0/_a))/2.0;
  }
  else {
    _y = 1.0 - (pow(2.0*(1.0-_x), 1.0/_a))/2.0;
  }
  return(_y);
}