Andre Le

21 Jan 2014

andrele_lenticular

 

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.

andrele_blackhole_lenticular

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:

IMG_1023 blackHole-Process IMG_1024

 

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();
  }
}