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):