Here’s my interactive animation, and here’s the code:
the main program:
ArrayList squigglies; // the arraylist that holds the squigglies int wiggleroom = 1; // amount the squiggles are allowed to wiggle int lifespan = 20; // number of joints per squiggle int numSquigglies =6; // number of squiggles per row int rows = 3; // number of rows int xborder = 200; // buffer on left/right int yborder = 100; // buffer on top/bottom color bgcolor = color(255, 240, 200, 127); // background color int R = 1; // foreground red int G = 3; // foreground green int B = 10; // foreground blue boolean out; // toggle to change direction (imploding or exploding) boolean looping = true; int tlife = 255; // life of the text at intro float w, wp, wr; // text widths String pause = "spacebar to pause"; // intro text String s = "click to explode / implode"; // intro text String reset = "r key to reset"; // intro text PFont font; // font (unused in web version because web fonts suck) PVector anchor = new PVector(0, 0); // explosion / implosion base point void setup() { size(screen.width, screen.height); background(bgcolor); smooth(); noFill(); out = true; // initially explode font = loadFont("LetterGothic.vlw"); // setup font textFont(font, 24); w = textWidth(s); // get text widths wp = textWidth(pause); wr = textWidth(reset); squigglies = new ArrayList(); // initialize the squigglies arraylist for (int b = 0; b < rows; b++) { // cycle through the rows for (int i = 0; i < numSquigglies; i++) { // cycle through squigglies float x = i*((width-xborder)/numSquigglies) + random(-(width-xborder)/numSquigglies/2, (width-xborder)/numSquigglies/2); // splay out along the x-axis, randomly float y = height-yborder; // all along one base y x += xborder/2 + (width-xborder)/numSquigglies/2; // add border PVector spawn = new PVector(x, y); // where to put the squiggly squigglies.add(new Squiggly(spawn, color(R, G, B, (b+1)*(255/rows)))); // add a squiggly to the arraylist at spawn(x,y), // with a foreground color, shaded according to row } } for (int j = 0; j < squigglies.size(); j++) { // loop through the (now full) array of squigglies Squiggly squiggle = (Squiggly) squigglies.get(j); // cast the object coming out // (arraylists don't know what they hold) for (int k = 0; k < lifespan; k++) { // for the length of the lifespan, // figure out where to drop a joint float x2 = squiggle.root.x + random(-wiggleroom, wiggleroom); float y2 = (height-yborder) - ((height-2*yborder)/lifespan)*k; if (k == lifespan-1) { y2 -= random(((height-2*yborder)/lifespan)); // add some noise } PVector Loc = new PVector(x2, y2); squiggle.addJoint(Loc); // add the joint into the squiggly } } } void draw() { fill(bgcolor); noStroke(); rect(0, 0, width, height); // medium transparent background creates trailing noFill(); if (squigglies.size() > 0) { // if there are squigglies for (int i = 0; i < squigglies.size(); i++) { Squiggly squiggle = (Squiggly) squigglies.get(i); // get the squiggly (& cast it) squiggle.wiggle(); // wiggle the squiggle! squiggle.show(); // show it } } if (tlife > 0) { // intro text fill(red(bgcolor), green(bgcolor), blue(bgcolor), tlife); noStroke(); rect(width/2-(w/2+100), height/2-110, w+200, 200); fill(R,G,B,tlife); text(s, width/2-w/2, height/2-40); text(pause, width/2-wr/2, height/2); text(reset, width/2-wr/2, height/2+40); tlife-=2; noFill(); } } void mousePressed() { out = !out; // switch direction for (int i = 0; i < squigglies.size(); i++) { Squiggly squiggle = (Squiggly) squigglies.get(i); // get & cast squiggly squiggle.essplode(); // essplode! } } void keyPressed() { if (key == ' ') { // pause on spacebar print(looping + " > "); if (looping) noLoop(); else if (!looping) loop(); looping = !looping; println(looping); } else if (key == 'r') { looping = true; // reset looping to true loop(); // then actually loop setup(); // then reset } }
the squiggly class:
class Squiggly { // variables: PVector root; int speed, age; ArrayList joints; color SquigglyColor; boolean increasing; Squiggly (PVector _loc, color _c) { // gets passed a location and color root = new PVector(_loc.x, _loc.y); joints = new ArrayList(); // initialize an array of joints joints.add(new Joint(root)); // dump a joint at the root age = 1; SquigglyColor = _c; } void addJoint(PVector jLoc) { age++; // with new joints, increment age joints.add(new Joint(jLoc)); // and add a joint } void wiggle() { // wiggle it! for (int i = 0; i < age; i++) { // cycle through the number of joints Joint joint = (Joint) joints.get(i); // cast the joint joint.makeSomeNoise(); // wiggle it! } } void show() { stroke(SquigglyColor); beginShape(); if (age > 1) { for (int i = 0; i < age; i++) { // for each joint, Joint joint = (Joint) joints.get(i); // get + cast joint.update(); // move it curveVertex(joint.loc.x, joint.loc.y); // add a vertex at the joint's location if (joint.loc.x > width+10 || joint.loc.x < -10 || joint.loc.y > height+10 || joint.loc.y < -10) { // if the joint is off the screen joint.velocity.x = 0; // stop its motion joint.velocity.y = 0; joint.acceleration.x = 0; joint.acceleration.y = 0; age--; // make the squiggly younger joints.remove(i); // remove this joint from the squiggly // (connects the joints on either side) } } Joint last = (Joint) joints.get(age-1); // put an extra vertex at the end curveVertex(last.loc.x, last.loc.y); // because of the way beginshape() works } endShape(); } void essplode() { anchor = new PVector(mouseX, mouseY); // set the explosion point to the mouse location stroke(R,G,B, 10); // color = foreground color, alpha 10 ellipse(anchor.x, anchor.y, 50, 50); // drop a quick circle for (int i = 0; i < age; i++) { // for each joint Joint joint = (Joint) joints.get(i); // get & cast joint.essploding = true; // set the joint to essplode joint.atang = atan2(joint.loc.y-anchor.y, joint.loc.x-anchor.x); // calculate the direction of explosion } } }
the joint class:
class Joint { // all the variables PVector loc, velocity, acceleration; // physics variables float xadd, yadd, xoff, yoff, xinc, yinc, angle, atang; // animation (math) variables boolean essploding = false; // toggle for exploding int timer; Joint (PVector _loc) { // gets passed the location loc = new PVector(_loc.x, _loc.y); velocity = new PVector(0, 0); acceleration = new PVector(0, 0); // initialize acceleration xoff = random(1); // setup x noise yoff = random(1); // setup y noise xinc = random(0.003, 0.03); // setup x step yinc = random(0.003, 0.03); // setup y step angle = random(TWO_PI); // setup sin/cos calculator } void makeSomeNoise() { velocity.x = noise(xoff); // get perlin noise velocity.y = noise(yoff); velocity.x *= cos(angle)*wiggleroom; // scale it according to velocity.y *= sin(angle)*wiggleroom; // a circle, to go back + forth xoff += xinc; // increment through noise space yoff += yinc; angle+= random(.1, .5); // increment angle } void update() { velocity.add(acceleration); // because of derivatives... loc.add(velocity); // this is how physics animation works if (essploding == true && out == true) { // if exploding, acceleration.x = cos(atang)*3; // x = cosine of explosion angle acceleration.y = sin(atang)*3; // y = sine of explosion angle } else if (essploding == true && out == false) { // if imploding, acceleration.x = -cos(atang)*3; // inverse of exploding acceleration.y = -sin(atang)*3; } } }
here’s my attempt at non-linear animation – the movement of the lines is governed by perlin noise and sin/cosine oscillation.
here’s the code:
main program:
ArrayList squigglies; int res = 10; int wiggleroom = 1; int lifespan = 10; int numSquigglies =10; int xborder = 200; int yborder = 100; color bgcolor = color(255, 240, 200, 50); int R = 1; int G = 3; int B = 10; PVector anchor = new PVector(0, 0); void setup() { size(screen.width, screen.height); background(0); smooth(); noFill(); squigglies = new ArrayList(); for (int b = 0; b < 5; b++) { for (int i = 0; i < numSquigglies; i++) { float x = i*((width-xborder)/numSquigglies) + random(-(width-xborder)/numSquigglies/2, (width-xborder)/numSquigglies/2); float y = height-yborder; x += xborder/2 + (width-xborder)/numSquigglies/2; PVector spawn = new PVector(x, y); squigglies.add(new Squiggly(spawn, color(R, G, B, (b+1)*25))); } } for (int j = 0; j < squigglies.size(); j++) { Squiggly squiggle = (Squiggly) squigglies.get(j); for (int k = 0; k < lifespan; k++) { float x2 = squiggle.root.x + random(-wiggleroom, wiggleroom); float y2 = (height-yborder) - 80*k; if (k == lifespan-1) { y2 -= random(160); } PVector Loc = new PVector(x2, y2); squiggle.addJoint(Loc); } } } void draw() { fill(bgcolor); noStroke(); rect(0, 0, width, height); noFill(); if (squigglies.size() > 0) { for (int i = 0; i < squigglies.size(); i++) { Squiggly squiggle = (Squiggly) squigglies.get(i); squiggle.wiggle(); squiggle.show(); } } }
the joint class:
class Joint { PVector loc, velocity, acceleration; float xadd, yadd, xoff, yoff, xinc, yinc, angle; Joint (PVector _loc) { loc = new PVector(_loc.x, _loc.y); velocity = new PVector(0,0); acceleration = new PVector(0,0); xoff = random(1); yoff = random(1); xinc = random(0.003, 0.03); yinc = random(0.003, 0.03); angle = random(TWO_PI); } void makeSomeNoise() { velocity.x = noise(xoff); velocity.y = noise(yoff); velocity.x *= cos(angle)*wiggleroom; velocity.y *= sin(angle)*wiggleroom; xoff += xinc; yoff += yinc; angle+= random(.1,.5); } void update() { loc.add(velocity); velocity.add(acceleration); } }
the squiggly class:
class Squiggly { PVector root; int age; ArrayList joints; color SquigglyColor; Squiggly (PVector _loc, color _c) { root = new PVector(_loc.x, _loc.y); joints = new ArrayList(); joints.add(new Joint(root)); age = 1; stroke(R,G,B); ellipse(root.x, root.y, 5, 5); SquigglyColor = _c; } void addJoint(PVector jLoc) { age++; joints.add(new Joint(jLoc)); } void wiggle() { for (int i = 0; i < age; i++) { Joint joint = (Joint) joints.get(i); if (i > 1) { joint.makeSomeNoise(); } } } void show() { stroke(SquigglyColor); beginShape(); if (age > 1) { for (int i = 0; i < age; i++) { Joint joint = (Joint) joints.get(i); joint.update(); curveVertex(joint.loc.x, joint.loc.y); } Joint last = (Joint) joints.get(age-1); curveVertex(last.loc.x, last.loc.y); } endShape(); } }
As with most things, I dramatically underestimated the time it takes to animate a figure. As the screenshots below indicate, I got about halfway through, and faced with the staggering sisyphean task of animating hundreds more keyframes, I found a way to reverse part of the work I’d already done, to create the kind of looping effect I had in mind without redoing all the keyframes. As a result, the overall timing isn’t perfect, and it doesn’t loop back to the beginning, as I had hoped – a project that will require more time and sleep…
The story of Sisyphus holds much of its power in its sheer simplicity. In the Greek mythology, Sisyphus was a brutal and cunning king who was clever enough to outwit Thanatos (the god of death) and escape from the underworld. As punishment, Sisyphus is tasked with eternally pushing a massive boulder up a mountain, only to have it roll back down again. The mind-numbingly perpetual aspect of this story (the perfect punishment for the craftiest man) is especially intriguing for my purposes, as it is perfectly suited for the creation of an animated infinite loop. Aesthetically, too, I’m hoping to create allusions to the history of looping animation – especially the rolling loop backdrops used in film to simulate driving and the waves of animated GIF fads of the last few decades. I’m hoping that, visually, a kind of rapidly-shifting digital collage will enhance the sense of universality that infuses Greek mythology.

this is sisyphus’s boulder – I realized quickly that animating sisyphus himself was far too ambitious…