Tesh-AnimatedLoop

 

For my GIF I wanted to make something minimalist and snappy that feels satisfying to watch. The shapes are generated by lines whose endpoints I am interpolating between start and end positions using the Penner Ease Out Elastic function. This easing function starts at “0,” overshoots and then comes back to “1,” which gives it a more abrupt ending than other functions and emphasizes the end positions. I then added a shadow to make the shape in the center pop out more, and then brought it forwards to change the background color to allow for a smooth loop.

The hardest part for me was figuring out the design concept I wanted to go for. I knew I wanted it to be snappy so I found that function first, and I originally had the lines break away and run offscreen rather than having the shape increase to fill the screen. I would like to add more complexity to this animation but I am struggling to find where to add more without reworking some of the underlying framework. I think adding more points and creating an animation that transitions between more shapes would also probably make the GIF a lot more interesting.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
// 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 = "Tesh";
var nFramesInLoop = 65;
var bEnableExport = true;
 
// Other global variables you don't need to touch.
var nElapsedFrames;
var bRecording;
var theCanvas;
 
//===================================================
function setup() {
  theCanvas = createCanvas(600, 600);
  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) {
  //
  // 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!
 
 
 
 
  var size = 30;
  var func;
  var func2;
  var func3;
  var timeInt = millis() / 1000;
 
  var A;
  var B;
  var C;
  var D;
 
  if (timeInt < 3) background(254, 86, 34); else if (timeInt >= 3 && timeInt <3.4)
    background(lerpColor(color(254, 86, 34), color(160, 90, 94), function_PennerEaseOutElastic((millis() % 1000) / 1000)));
  else
    background(160, 90, 94);
 
  if (timeInt <= 2) {
    func = width / 3 * function_PennerEaseOutElastic((millis() % 1000) / 1000);
    func2 = width / 3;
    func3 = func2;
  } else if (timeInt <= 2) {
    func = width / 3;
    func2 = width / 3;
    func3 = func2;
  } else if (timeInt <= 3) {
    func = lerp(width / 3, width / 2 - width / 6 * sqrt(2), function_PennerEaseOutElastic((millis() % 1000) / 1000));
    func2 = lerp(width / 3, width / 2, function_PennerEaseOutElastic((millis() % 1000) / 1000));
    func3 = width / 2 - width / 6 * sqrt(2);
  } else if (timeInt <= 4) {
    func = width / 2 - width / 6 * sqrt(2);
    func2 = width / 2;
    func3 = lerp(width / 2 - width / 6 * sqrt(2), -45, function_PennerEaseOutElastic((millis() % 1000) / 1000));
 
 
  } else if (timeInt <= 5) {
    func = lerp(width / 2 - width / 6 * sqrt(2), -320, function_PennerEaseOutElastic((millis() % 1000) / 1000));
    func2 = width / 2;
    func3 = -45;
 
  }
 
  /*ellipse(func2,func,size,size);
  ellipse(width-func,func2,size,size);
  ellipse(func,height-func2,size,size);
  ellipse(width-func2,height-func,size,size);
  */
  stroke(0, 0, 0);
  strokeWeight(3);
 
  /*A = point(func2,func);
  B = point(width-func,func2);
  C = point(width-func2,height-func);
  D = point(func,height-func2);
  */
 
  if (timeInt <= 1) strokeWeight(0); line(func2, func, width - func, func2); line(width - func, func2, width - func2, height - func); line(width - func2, height - func, func, height - func2); line(func, height - func2, func2, func); var off = 12; stroke(215, 76, 74); strokeWeight(0); if (timeInt == 2) strokeWeight(lerp(0, 15, function_PennerEaseOutElastic((millis() % 1000) / 1000))); else if (timeInt > 2)
    strokeWeight(15);
 
  strokeCap(SQUARE);
  line(func2 + off, func, width - func + off, func2);
 
  strokeCap(PROJECT);
  line(width - func + off, func2, width - func2 + off, height - func);
  //line(width-func2+off,height-func,func+off,height-func2);
  //line(func+off,height-func2,func2+off,func);
 
 
  stroke(0);
  if (timeInt < 3)
    stroke(0, 0, 0, 0);
 
  strokeWeight(4);
  line(func2, func, func2, func3);
  line(width - func, func2, width - func3, func2);
  line(width - func2, height - func, width - func2, height - func3);
  line(func, height - func2, func3, height - func2);
 
  strokeWeight(1);
  fill(254, 86, 34);
  beginShape();
  vertex(func2, func);
  vertex(width - func, func2);
  vertex(width - func2, height - func);
  vertex(func, height - func2);
  vertex(func2, func);
  endShape();
 
  /*
    //----------------------
    // here, I set the background and some other graphical properties
    background(180);
    smooth();
    stroke(0, 0, 0);
    strokeWeight(2);
 
    //----------------------
    // Here, I assign some handy variables. 
    var cx = 100;
    var cy = 100;
 
    //----------------------
    // Here, I use trigonometry to render a rotating element.
    var radius = 80;
    var rotatingArmAngle = percent * TWO_PI;
    var px = cx + radius * cos(rotatingArmAngle);
    var py = cy + radius * sin(rotatingArmAngle);
    fill(255);
    line(cx, cy, px, py);
    ellipse(px, py, 20, 20);
 
    //----------------------
    // Here, I use graphical transformations to render a rotated square. 
    // Notice the use of push(), pop(), translate(), etc. 
    push();
    translate(cx, cy);
    var rotatingSquareAngle = percent * TWO_PI * -0.25;
    rotate(rotatingSquareAngle);
    fill(255, 128);
    rect(-40, -40, 80, 80);
    pop();
 
    //----------------------
    // Here's a linearly-moving square
    var squareSize = 20;
    var topY = 0 - squareSize - 2;
    var botY = height + 2;
    var sPercent = (percent + 0.5)%1.0; // shifted by a half-loop
    var yPosition = map(sPercent, 0, 1, topY, botY);
    fill(255, 255, 255);
    rect(230, yPosition, 20, 20);
 
    //----------------------
    // Here's a sigmoidally-moving pink square!
    // This uses the "Double-Exponential Sigmoid" easing function 
    // ripped from From: https://idmnyu.github.io/p5.js-func/
    // Really, you should just include this library!!
    var eased = doubleExponentialSigmoid (percent, 0.7); 
    eased = (eased + 0.5)%1.0; // shifted by a half-loop, for fun
    var yPosition2 = map(eased, 0, 1, topY, botY); 
    fill (255, 200, 200); 
    rect (260, yPosition2, 20, 20); 
 
    //----------------------
    // Here's a pulsating ellipse
    var ellipsePulse = sin(3.0 * percent * TWO_PI);
    var ellipseW = map(ellipsePulse, -1, 1, 20, 50);
    var ellipseH = map(ellipsePulse, -1, 1, 50, 30);
    var ellipseColor = map(ellipsePulse, -1, 1, 128, 255);
    fill(255, ellipseColor, ellipseColor);
    ellipse(350, cy, ellipseW, ellipseH);
 
    //----------------------
    // Here's a traveling sine wave
    stroke(0, 0, 0);
    for (var sy = 0; sy <= height; sy += 4) {
      var t = map(sy, 0, height, 0.0, 0.25);
      var sx = 450 + 25.0 * sin((t + percent) * TWO_PI);
      ellipse(sx, sy, 1, 1);
    }
 
    //----------------------
    // Include some visual feedback. 
    fill(255, 0, 0);
    noStroke();
    textAlign(CENTER);
    var percentDisplayString = "" + nf(percent, 1, 3);
    text(percentDisplayString, cx, cy - 15);
    */
}
 
 
// 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 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);
}
 
function function_PennerEaseOutElastic(t) {
 
  if (t == 0) return 0.0;
  if (t == 1) return 1.0;
  var p = 0.3;
  var s = p / 4;
 
  return (pow(2, -10 * t) * sin((t - s) * (2 * PI) / p) + 1);
}