The above gif is actually the second animation I completed for this project. I originally wanted to create a black hole that continuously swallowed itself up. I played with lines, circles, and spirals to convey the illusion of the outer mass sucking in towards the center, but after doing a few tests, I realized that the amount of motion wouldn’t lend well to a lenticular print.
What I really wanted was to play with seamless eternity. Something that you would fixate upon without realizing it. I took inspiration from a spinning umbrella and Pittsburgh’s endless downpour of rain and snow.
In order to play with animation curves, I imported a library called Ani to help with timing the animations and working with animation curves. It proved to be a very useful library for prototyping, but I felt that the preset animation curves could be limiting.
Overall, this was a great experience that I feel I could apply to loading animations, video loops, etc.
Sketches of process:
Code for Refill Animation:
import de.looksgood.ani.*; String myName = "andrele"; int SKETCH_SIZE = 1500; int nFramesInLoop = 10; int nElapsedFrames; float EDGE = SKETCH_SIZE/2*.75; int sides = 6; float angleIncrement = 360/sides; float overlap = .7; boolean bRecording; color fillColor = color(0,146,252); Ani rotationAni, insertAni; float rotation = -60; float insertPos = -EDGE; Segment insert; Segment[] segments; void setup() { size (SKETCH_SIZE,SKETCH_SIZE); frameRate(nFramesInLoop); noStroke(); smooth(); nElapsedFrames = 0; Ani.init(this); Ani.setDefaultTimeMode(Ani.FRAMES); segments = new Segment[sides]; for (int i = 0; i < sides; i++) { segments[i] = new Segment(angleIncrement*i, EDGE, fillColor, (float)(255/(float)sides*((float)i+1))); segments[i].opacityAnimation = new Ani(segments[i], nFramesInLoop*.5, "opacity", segments[i].opacity - (float)(255/(float)sides), Ani.SINE_IN_OUT); } rotationAni = new Ani(this, nFramesInLoop*.5, "rotation", rotation-60, Ani.SINE_IN_OUT); // Draw segment off-screen insert = new Segment(-120, 0, fillColor, 255); insertAni = new Ani(insert, nFramesInLoop*.5, nFramesInLoop*.5, "size", EDGE, Ani.SINE_IN_OUT); } void keyPressed() { // Press a key to export frames to the output folder bRecording = true; nElapsedFrames = 0; } void draw() { background(0); translate(SKETCH_SIZE/2, SKETCH_SIZE/2); pushMatrix(); rotate(radians(rotation)); // If we've received the frames in the loop, reset the animation if (frameCount%nFramesInLoop == 0) { for (Segment segment:segments){ segment.opacityAnimation.start(); } insertAni.start(); rotationAni.start(); } // float percentage = (frameCount%nFramesInLoop)/(float)nFramesInLoop; for (int i = 0; i < sides; i++) { segments[i].draw(); } pushStyle(); popStyle(); popMatrix(); insert.draw(); if (bRecording) { saveFrame("output/"+ myName + "-loop-" + nf(nElapsedFrames, 4) + ".png"); nElapsedFrames++; if (nElapsedFrames == nFramesInLoop) { bRecording = false; } } } class Segment { float angle, size, opacity; color fill; Ani opacityAnimation; Segment(float angle, float size, color fill, float opacity) { this.angle = angle; this.size = size; this.fill = fill; this.opacity = opacity; } void draw() { pushStyle(); color newColor = color(red(fillColor), green(fillColor), blue(fillColor), opacity); fill(newColor); triangle(0,0, size*cos(radians(angle)), size*sin(radians(angle)), size*cos(radians(angle+angleIncrement)), size*sin(radians(angle+angleIncrement))); popStyle(); } } |
Code for “Black Hole” animation:
import de.looksgood.ani.*; int nElapsedFrames; boolean bRecording; String myName = "andrele"; int MASTER_RADIUS = 200; int MAX_FRAMES = 30; int INNER_CIRCLES = 8; float LINE_WEIGHT = 60; Blob defaultBlob, whiteBlob, blackBlob; Blob[] innerBlobs; AniSequence innerSeq; Ani whiteAnimation, blackAnimation, initialAnimation; Line[] innerLines; void setup() { size(500,500, P2D); frameRate(MAX_FRAMES); smooth(); ellipseMode(RADIUS); noStroke(); background(255); fill(0); defaultBlob = new Blob(0, 0, MASTER_RADIUS, color(0)); blackBlob = new Blob(0, 0, MASTER_RADIUS/2, color(0)); whiteBlob = new Blob(0, 0, 0, color(255)); Ani.init(this); Ani.setDefaultTimeMode(Ani.FRAMES); whiteAnimation = new Ani(whiteBlob, MAX_FRAMES, "radius", MASTER_RADIUS, Ani.QUAD_OUT); blackAnimation = new Ani(blackBlob, MAX_FRAMES*.20, MAX_FRAMES*.80, "radius", MASTER_RADIUS, Ani.QUAD_IN_OUT, "onEnd:animationEnd"); initialAnimation = new Ani(blackBlob, MAX_FRAMES*.7, "radius", 40, Ani.SINE_OUT, "onEnd:initialAniEnd"); whiteAnimation.start(); initialAnimation.start(); blackAnimation.start(); innerLines = new Line[INNER_CIRCLES]; float angleIncrement = 360/INNER_CIRCLES; for (int i = 0; i < INNER_CIRCLES; i++) { innerLines[i] = new Line(0, MASTER_RADIUS-(LINE_WEIGHT/2), (i+1)*angleIncrement, LINE_WEIGHT, color(0)); } // innerBlobs = new Blob[INNER_CIRCLES]; // for (int i = 0; i < INNER_CIRCLES; i++) { // innerBlobs[i] = new Blob((MASTER_RADIUS-LINE_WEIGHT/2)*cos(radians(angleIncrement*i)), (MASTER_RADIUS-LINE_WEIGHT/2)*sin(radians(angleIncrement*i)), LINE_WEIGHT/2, color(0)); // innerBlobs[i].moveAnimation = new Ani(innerBlobs[i], MAX_FRAMES/2, MAX_FRAMES/2, "rEnd", 0, Ani.SINE_IN_OUT); // innerBlobs[i].sizeAnimation = new Ani(innerBlobs[i], MAX_FRAMES-10, 10, "radius", 0, Ani.QUART_IN_OUT); // } } void keyPressed() { // Press a key to export frames to the output folder bRecording = true; nElapsedFrames = 0; } void draw() { background(255); pushStyle(); pushMatrix(); translate(width/2, height/2); defaultBlob.draw(); whiteBlob.draw(); // Draw swirl inbetween // float swirlR; // if (whiteBlob.radius<MASTER_RADIUS) { // swirlR = (whiteBlob.radius + blackBlob.radius)/2; // } else { // swirlR = (whiteBlob.radius - blackBlob.radius)/2; // } // float swirlX = (swirlR-blackBlob.radius); // float swirlAngle = 360 * ( (float)frameCount%(float)MAX_FRAMES/(float)MAX_FRAMES ); //// float swirlAngle = 45; // float swirlOffset = 90; // println(swirlAngle); // fill(0); // ellipse(swirlX*cos(radians(swirlAngle)),swirlX*sin(radians(swirlAngle)),swirlR,swirlR); // arc(0,0,whiteBlob.radius < MASTER_RADIUS ? whiteBlob.radius : 0,whiteBlob.radius < MASTER_RADIUS ? whiteBlob.radius : 0, radians(swirlAngle-(swirlOffset*.6)-23), radians(swirlAngle+(swirlOffset*.6)-23)); // fill(255); // ellipse(swirlX*cos(radians(swirlAngle-swirlOffset)),swirlX*sin(radians(swirlAngle-swirlOffset)), swirlR, swirlR); for (Line line:innerLines) { line.draw(); } // for (Blob blob:innerBlobs) { // blob.draw(); // } // blackBlob.draw(); // for (int i = 0; i < innerBlobs.length; i++) { // innerBlobs[i].draw(); // } popMatrix(); popStyle(); // If we're recording the output, save the frame to a file. if (bRecording) { saveFrame("output/"+ myName + "-loop-" + nf(nElapsedFrames, 4) + ".png"); nElapsedFrames++; if (nElapsedFrames == MAX_FRAMES) { bRecording = false; } } } class Blob { float x, y, radius; color circleColor; Ani sizeAnimation, moveAnimation; Blob( float x, float y, float radius, color circleColor ) { this.x = x; this.y = y; this.radius = radius; this.circleColor = circleColor; } void draw() { pushStyle(); fill(circleColor); ellipse(x,y,radius,radius); popStyle(); } void animationEnd(Ani theAni) { println("Black animation end"); whiteAnimation.start(); initialAnimation.start(); blackAnimation.start(); for (Line line : innerLines) { line.lengthAnimation.start(); line.weightAnimation.start(); } // for (Blob blob : innerBlobs) { // blob.sizeAnimation.start(); // blob.moveAnimation.start(); // } } } class Line { float rStart, rEnd, angle, weight; color lineColor; Ani lengthAnimation, weightAnimation; Line( float rStart, float rEnd, float angle, float weight, color lineColor ) { this.rStart = rStart; this.rEnd = rEnd; this.angle = angle; this.weight = weight; this.lineColor = lineColor; this.lengthAnimation = new Ani(this, MAX_FRAMES*.4, MAX_FRAMES*.6, "rEnd", 0, Ani.SINE_IN_OUT); this.weightAnimation = new Ani(this, MAX_FRAMES*.65, MAX_FRAMES*.03, "weight", 0, Ani.SINE_IN); } float getX(float radius){ return radius * cos(radians(angle)); } float getY(float radius){ return radius * sin(radians(angle)); } void draw(){ this.rEnd = MASTER_RADIUS-(this.weight/2); pushStyle(); strokeWeight(weight); stroke(lineColor); line(getX(rStart),getY(rStart),getX(rEnd),getY(rEnd)); popStyle(); } } |