sheep-AnimatedLoop

My experience creating this piece was a lot of finnicky changing of entering into the easing functions to get the gif looking just right. Over all, I was pretty proud how it ended up. I used a lot of easing functions for this: Penner Ease Out Bounce, the Iterative Square Root, the Flat Top Window, the Blackman-Harris Window, the Hann Window, the General Sigmoid Logit Combination, the Half Blackman-Nuttall Window, the Penner Ease-In Expo, and the Double Exponential Sigmoid. The bounce was specifically selected because of its similarity to gravitational forces. I think the amount of easing functions I used shows. The initial design wasn’t that much dissimilar from the final version, though I had intended to make the view less 2D and more isometric.

This was the initial plan, including some hastily written functions and a storyboard.

 

// 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
// gif loop - sheep
//===================================================
// Global variables. 
String  myNickname = "sheep"; 
int     nFramesInLoop = 120;
int     nElapsedFrames;
boolean bRecording; 
float linear = 0;
//===================================================
void setup() {
  size (500, 500); 
  bRecording = false;
  nElapsedFrames = 0;
  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 >= nFramesInLoop) {
      bRecording = false;
    }
  }
}
 
//===================================================
void renderMyDesign (float percent) {
  background (234, 90, 56);
  smooth(); 
  stroke (0, 0, 0); 
  strokeWeight (2); 
 
  //----------------------
  // set up loop for 3 seesaws
  for (int i = 0; i< 3; i++) {
    float cx = width/2;
    float cy = height/2;
    float gx = (width/2)+30;
    float gy = (height/2)-100;
    float rot = (percent/2.5)*(TWO_PI);
    //set up for different seesaw variable locations
    if (i==0) {
      cx = (width/2)-150;
      cy = (height/2);
      gx = ((width/2)+30)-150;
      gy = (height/2)-100;
      rot = (percent/2.5)*(TWO_PI);
    }
    if (i==1) {
      cx = (width/2);
      cy = (height/2);
      gx = ((width/2)+30);
      gy = (height/2)-100;
      rot = (percent/2.5)*(TWO_PI);
    }
    if (i==2) {
      cx = (width/2)+150;
      cy = (height/2);
      gx = ((width/2)+30)+150;
      gy = (height/2)-100;
      rot = (percent/2.5)*(TWO_PI);
    }
    //center eveyrthing
    rectMode(CENTER);
    ellipseMode(CENTER);
 
 
    //shadow of seesaws
    pushMatrix();
 
    noStroke();
    fill(200, 0, 0, 40);
    if (percent<.1) {
      translate(cx, cy+(function_FlatTopWindow (map(percent, 0, 1, 0.5, 5.5))*300));
    } else if (percent<.7) {
      translate(cx, cy);
    } else {
 
      translate(cx+(100*function_IterativeSquareRoot (map(percent, 0.7, 1, 0, 1))), cy+(850*function_HannWindow(map(percent, .7, 1, 0, .5))));
    }
    scale(map(constrain(percent, 0.5, .7), 0.5, 1, 1, 3.8));
    float radius = 35;
    float angle0 = function_PennerEaseInExpo((percent/2.5)*(TWO_PI));
    float angle1 = function_PennerEaseInExpo((percent/2.5)*(TWO_PI))+PI;
 
    if (percent>.625) {
      angle0 = 52;
      angle1 = 55;
    }
    if (percent>.71) {
      scale(percent);
    }
    beginShape();
    vertex(0-(radius*cos(angle1)), 0-(radius*sin(angle1)));
    vertex(0-(radius*cos(angle0)), 0-(radius*sin(angle0)));
    vertex(500-(radius*cos(angle0)), 200-(radius*sin(angle0)));
    vertex(500-(radius*cos(angle1)), 200-(radius*sin(angle1)));
    vertex(0-(radius*cos(angle1)), 0-(radius*sin(angle1)));
    endShape();
 
    popMatrix();
    //endshadow
 
    //pegshadow
    pushMatrix();
    noStroke();
    if (percent<.1) {
      translate(cx, cy+(function_FlatTopWindow (map(percent, 0, 1, 0.5, 5.5))*300));
    } else if (percent<.85) {
      translate(cx, cy);
    } else {
 
      translate(cx, cy-(function_FlatTopWindow (map(percent, 0.85, 1, 0, .5))*300));
      scale(1-(function_FlatTopWindow (map(percent, 0.85, 1, 0, .3))));
    }
 
 
    fill(80, 0, 0, 40);
    beginShape();
    vertex(-5, 0);
    vertex(5, 0);
    vertex(60, 30);
    vertex(60, 30);
    vertex(-5, 0);
 
    endShape();
 
    popMatrix();
    //endpegshadow
    //seesaw
    pushMatrix();
 
 
    if (percent<.1) {
      translate(cx, cy+(function_FlatTopWindow (map(percent, 0, 1, 0.5, 5.5))*300));
    } else if (percent<.7) {
      translate(cx, cy);
    } else {
      translate(cx+(100*function_IterativeSquareRoot (map(percent, 0.7, 1, 0, 1))), cy+(850*function_HannWindow(map(percent, .7, 1, 0, .5))));
    }
    scale(map(constrain(percent, 0.5, .7), 0.5, 1, 0.5, 2));
 
    if (percent>.71) {
      scale(percent);
    }
    rotate(function_PennerEaseInExpo (rot));
 
    fill(1, 133, 148);
    arc(0, 0, 150, 150, 0, constrain(map(TWO_PI*(constrain(function_PennerEaseInExpo(percent)*15, 0, .99)), 0, TWO_PI, -1, TWO_PI*2), 0, TWO_PI), PIE);
    fill(1+(254*percent), 133+(percent*(255-133)), 148+(percent*(255-148)), 255-(percent*100));
    rect(0, 0, 150, 20);
    fill(15, 64, 100);
    ellipse(0, 0, 20, 20);
    fill(234, 90, 56);
    ellipse(0, 0, 10, 10);
 
 
 
    popMatrix();
 
    //endseesaw
    //spinnerenter
 
 
    pushMatrix();
    if ((percent*2) < (1/2.75f)) {
      linear = 0;
    } else {
      linear+=4;
    }
 
    fill(1, 133, 148);
    translate(gx+linear, gy+500*function_PennerEaseOutBounce (percent*2)-430);
 
    scale(.5);
    rotate(percent*100);
    arc(0, 0, 150, 150, 0, TWO_PI, PIE);
    fill(255, 255, 255, 100);
    rect(0, 0, 150, 20);
    fill(15, 64, 100);
    ellipse(0, 0, 20, 20);
    fill(234, 90, 56);
    ellipse(0, 0, 10, 10);
    popMatrix();
    //spinnter
    //bottompeg
    fill(255);
    pushMatrix();
 
    if (percent<.1) {
      translate(cx, cy+(function_FlatTopWindow (map(percent, 0, 1, 0.5, 5.5))*300));
    } else if (percent<.85) {
      translate(cx, cy);
    } else {
 
      translate(cx, cy-(function_FlatTopWindow (map(percent, 0.85, 1, 0, .5))*300));
      scale(1-(function_FlatTopWindow (map(percent, 0.85, 1, 0, .3))));
    }
    if (percent<.71 || percent> .725) {
      ellipse(0, 0, 10, 10);
    }
    popMatrix();
  }
}
 
 
 
//===================================================
// 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<=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_PennerEaseInExpo(float t) {
  //functionName = "Penner's EaseIn Exponential";
  return (t==0) ? 0 : pow(2, 10 * (t - 1));
}
float function_HalfBlackmanNuttallWindow (float x) {
  // http://en.wikipedia.org/wiki/Window_function 
  //functionName = "Blackman–Nuttall Window (Half)";
 
  final float a0 = 0.3635819;
  final float a1 = 0.4891775;
  final float a2 = 0.1365995;
  final float a3 = 0.0106411;
 
  x *= 0.5;
  float pix = PI*x;
  float y = a0 - a1*cos(2*pix) + a2*cos(4*pix) - a3*cos(6*pix);
  return y;
}
float function_GeneralSigmoidLogitCombo (float x, float a, float b) {
 
  float y = 0; 
  if (a < 0.5) {
    // Logit
    float dy = b - 0.5;
    y = dy + function_NormalizedLogit (x, 1.0-(2.0*a));
  } else {
    // Sigmoid
    float dx = b - 0.5;
    y = function_NormalizedLogisticSigmoid (x+dx, (2.0*(a-0.5)));
  }
 
  //functionName = "General Sigmoid-Logit Combination";
  y = constrain(y, 0, 1); 
  return y;
}
float function_NormalizedLogit (float x, float a) {
  // http://en.wikipedia.org/wiki/Logit
  //functionName = "Normalized Logit Function";
 
  float min_param_a = 0.0 + EPSILON;
  float max_param_a = 1.0 - EPSILON;
  float emph = 5.0;
 
  a = constrain(a, min_param_a, max_param_a); 
  a = (1/(1-a) - 1); 
  a = emph * a;
 
  float minx = 1.0 / (1.0 + exp(  0.5*a    ));
  float maxx = 1.0 / (1.0 + exp( -0.5*a    ));
  x = map(x, 0, 1, minx, maxx);
 
  float y = log ( x / (1.0 - x)) ;
  y *= 1.0/a; 
  y += 0.5;
 
  y = constrain (y, 0, 1); 
  return y;
}
float function_NormalizedLogisticSigmoid (float x, float a) {
  //functionName = "Normalized Logistic Sigmoid";
 
  float min_param_a = 0.0 + EPSILON;
  float max_param_a = 1.0 - EPSILON;
  float emph = 5.0;
 
  a = constrain(a, min_param_a, max_param_a);
  a = (1.0/(1.0-a) - 1.0); 
  a = emph * a;
 
  float y    = 1.0 / (1.0 + exp(0 - (x-0.5)*a    ));
  float miny = 1.0 / (1.0 + exp(  0.5*a    ));
  float maxy = 1.0 / (1.0 + exp( -0.5*a    ));
  y = map(y, miny, maxy, 0, 1); 
  return y;
}
 
float function_HannWindow (float x) {
  // http://en.wikipedia.org/wiki/Window_function 
  //functionName = "Hann (Raised Cosine) Window";
 
  float y = 0.5 * (1.0 - cos(TWO_PI*x));
  return y;
}
float function_BlackmanHarrisWindow (float x) {
  // http://en.wikipedia.org/wiki/Window_function 
  //functionName = "Blackman–Harris Window";
 
  final float a0 = 0.35875;
  final float a1 = 0.48829;
  final float a2 = 0.14128;
  final float a3 = 0.01168;
 
  float pix = PI*x;
  float y = a0 - a1*cos(2*pix) + a2*cos(4*pix) - a3*cos(6*pix);
  return y;
}
float function_FlatTopWindow (float x) {
  // http://en.wikipedia.org/wiki/Window_function 
  //functionName = "Flat Top Window";
 
  final float a0 = 1.000;
  final float a1 = 1.930;
  final float a2 = 1.290;
  final float a3 = 0.388;
  final float a4 = 0.032;
 
  float pix = PI*x;
  float y = a0 - a1*cos(2*pix) + a2*cos(4*pix) - a3*cos(6*pix) + a4*cos(8*pix);
  y /= (a0 + a1 + a2 + a3 + a4); 
 
  return y;
}
float function_IterativeSquareRoot (float x) {
  // http://en.wikipedia.org/wiki/Methods_of_computing_square_roots
  // Ancient Babylonian technology
  //functionName = "Iterative (Heron's) Square Root";
  float y = 0.5; 
  int n = 6;
  for (int i=0; i<n; i++) {
    y = (y + x/y)/2.0;
  }
  return y;
}
float function_PennerEaseOutBounce (float t) {
  //functionName = "Penner's EaseOut Bounce";
 
  if ((t) < (1/2.75f)) {
    return (7.5625f* t*t);
  } else if (t < (2/2.75f)) {
    float postFix = t-=(1.5f/2.75f);
    return (7.5625f*(postFix)*t + 0.75f);
  } else if (t < (2.5/2.75)) {
    float postFix = t-=(2.25f/2.75f);
    return (7.5625f*(postFix)*t + 0.9375f);
  } else {
    float postFix = t-=(2.625f/2.75f);
    return (7.5625f*(postFix)*t + 0.984375f);
  }
}