Shan Huang

21 Jan 2014

500 x 500 version (60 frames)

1500×1500 version (10 frames)

This lenticular animation is somewhat inspired by this noise wave Processing tutorial. I first tried Processing in the summer two years ago, and I particularly enjoyed playing around with Perlin noise which is the magic to many organic looking generative arts. So for this assignment I wanted to extend the tutorial to render something immersed in water.

I decided to use frog as the subject without much difficulty. The only other candidates were goldfish and jellyfish, whose connection with water seemed a little boringly obvious. I found a cute image of a frog and was thinking about ways to make it look less realistic to match the style of the generated wave. I came to the solution of triangulation and found a triangulation library from processing wiki. With image tessellated into triangles, I now had a collection of vertices and triangles that I could move around with Perlin noise.

In terms of my design process, there are two things worth mentioning. Firstly I stepped the noise parameter of the frog and the wave differently so that the frog appears to be moving slower than the wave. I hoped this could create the illusion that we are looking at the frog through water…. but not sure if it worked. I also adjusted the brightness of the triangles below the wave (again based on Perlin noise) so that it seems like the reflection of the wave casts light onto the frog.

Here’s the image of the frog I used.frog1Here’s another image of a frog that I considered using…

NGS5475 9705

Hmmmm… not so cute as the first one…

Sketches

sketch1

sketch2

sketch3sketch4

Code

import org.processing.wiki.triangulate.*;
 
float xoff = 0.005;
float yoff = 0.005;
float maxWidth = 100;
float maxStrokeWidth = 10;
float minStrokeWidth = 0;
float seed = 0;
float seed1 = 0;
float colorThres = 5;
float waveHeight = 300;
PImage img;
float xScale = 1.4;
float yScale = 1.5;
float xTrans = -80;
float yTrans = 100;
int count = 0;
 
ArrayList triangles = new ArrayList();
ArrayList points = new ArrayList();
ArrayList wavePoints = new ArrayList();
ArrayList imgPoints = new ArrayList();
ArrayList imgTriangles = new ArrayList();
 
void genWavePoints(){
for(int j = 0; j < width+20; j += 20){
PVector newP = new PVector(j, waveHeight);
wavePoints.add(newP);
}
}
 
void genImgPoints(){
imgPoints.clear();
for(int i = 0; i < 1000; i++){
PVector newP = new PVector(random(-50, img.width+50), random(-50, img.height+50));
color newC = img.get(int(newP.x), int(newP.y));
if(alpha(newC) > colorThres){
imgPoints.add(newP);
}
}
 
imgTriangles = Triangulate.triangulate(imgPoints);
}
 
float noiseTranslate(float x, float y, float seed){
return map(noise(xoff*x, seed+y*yoff), 0, 1, y-maxWidth/2, y+maxWidth/2);
}
 
float noiseTranslate(float x, float y, float seed, float maxWidth){
return map(noise(xoff*x, seed+y*yoff), 0, 1, y-maxWidth/2, y+maxWidth/2);
}
 
void setupDesign() {
size(750, 750);
img = loadImage("frog.png");
genWavePoints();
genImgPoints();
}
 
void renderMyDesign(float percentage) {
seed = (percentage > 0.5? (1-percentage) : percentage) * 3;
seed1 = (percentage > 0.5? (1-percentage) : percentage)* 0.75;
 
background(64);
 
noStroke();
fill(255);
beginShape();
for(int i = 0; i < wavePoints.size(); i++){
PVector p = (PVector)wavePoints.get(i);
vertex(p.x, noiseTranslate(p.x, p.y, seed));
}
vertex(width, height);
vertex(0, height);
endShape(CLOSE);
 
beginShape(TRIANGLES);
 
for (int i = 0; i < imgTriangles.size(); i++) {
Triangle t = (Triangle)imgTriangles.get(i);
noStroke();
PVector imgCenter = new PVector(t.p1.x/3+t.p2.x/3+t.p3.x/3, t.p1.y/3+t.p2.y/3+t.p3.y/3);
PVector center = new PVector(imgCenter.x*xScale+xTrans, imgCenter.y*yScale+yTrans);
PVector p1 = new PVector(t.p1.x*xScale+xTrans, yTrans+t.p1.y*yScale);
PVector p2 = new PVector(t.p2.x*xScale+xTrans, yTrans+t.p2.y*yScale);
PVector p3 = new PVector(t.p3.x*xScale+xTrans, yTrans+t.p3.y*yScale);
float alpha = center.y > waveHeight ? map(noiseTranslate(center.x, center.y, seed), center.y-maxWidth/2, center.y+maxWidth/2, 0, 1.8) : 1;
color c = img.get(int(imgCenter.x), int(imgCenter.y));
fill(int(red(c)*alpha), int(green(c)*alpha), int(blue(c)*alpha), alpha(c));
vertex(p1.x, p1.y > waveHeight? noiseTranslate(p1.x, p1.y, seed1, maxWidth*1.5) : p1.y);
vertex(p2.x, p2.y > waveHeight? noiseTranslate(p2.x, p2.y, seed1, maxWidth*1.5) : p2.y);
vertex(p3.x, p3.y > waveHeight? noiseTranslate(p3.x, p3.y, seed1, maxWidth*1.5) : p3.y);
}
endShape();
 
noStroke();
fill(255, 100);
beginShape();
for(int i = 0; i < wavePoints.size(); i++){
PVector p = (PVector)wavePoints.get(i);
vertex(p.x, noiseTranslate(p.x, p.y, seed));
}
vertex(width, height);
vertex(0, height);
endShape(CLOSE);
 
}
 
// This is a template for creating a looping animation in Processing.
// When you press a key, this program will export a series of images
// into an "output" directory located in its sketch folder.
// These can then be combined into an animated GIF.
// Prof. Golan Levin, January 2014 - CMU IACD
 
//===================================================
// Global variables.
 
int nFramesInLoop = 120; // for lenticular export, change this to 10!
int nElapsedFrames;
boolean bRecording;
 
String myName = "shanhuan";
 
//===================================================
void setup() {
bRecording = false;
nElapsedFrames = 0;
frameRate (nFramesInLoop);
setupDesign();
}
//===================================================
void keyPressed() {
// Press a key to export frames to the output folder
bRecording = true;
nElapsedFrames = 0;
}
 
//===================================================
void draw() {
 
// Compute a percentage (0...1) representing where we are in the loop.
float percentCompleteFraction = 0;
if (bRecording) {
percentCompleteFraction = (float) nElapsedFrames / (float)nFramesInLoop;
}
else {
float modFrame = (float) (frameCount % nFramesInLoop);
percentCompleteFraction = modFrame / (float)nFramesInLoop;
}
 
// Render the design, based on that percentage.
renderMyDesign (percentCompleteFraction);
 
// 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 == nFramesInLoop) {
bRecording = false;
}
}
}