Erosion GIF
Idea Sketches
After messing around in Processing for a while, I went back to the drawing board and came up with 3 ideas for a gif.
One was really macabre—a stickman getting pulled apart. Decided against that one.
The second idea was to do something cyclic with the idea of erosion—inspired by pearls which form around a grain of sand. In this animation, a white circle would get worn down by black or grey particles around it until it was only one white particle, then it would grow back to its original size, layer by layer.
My third idea was to animate ripples (at some level of fidelity), just to play with color and create something aesthetically pleasing and calming to look at.
Inspiration
By Reza Ali Also by Reza Ali by dvdpPearls/Erosion GIF (2nd Concept)
Self-Critique of GIF
My favorite part of creating this GIF was researching GIF art in general. I was impressed and inspired by the works of artists like Reza Ali and dvdp. However, actually creating the types of effects in their GIFs, and creating them at high quality, proved much more difficult than I was expecting. I don’t think my GIF lives up to what I had in my head when I was sketching; the pace of the growth and shrinking of the pearl feels off, as does the motion of the “sand”. I learned a ton from the project; exploring GIFs as a medium, and OOP and particle systems in Processing . That said, I think that the concept I had in mind is one that I will have to come back to in order to execute as well as I would like.
Code
Main
// Global variables
// for GIF recording template
int nFramesInLoop = 120;
int nElapsedFrames;
boolean bRecording;
// for sketch
ParticleSystem sand;
Pearl pearl;
color backgroundColor = color(0);
void setup() {
size(600,600);
pearl = new Pearl(width/2, height/2);
sand = new ParticleSystem(new PVector(width/2, 50));
// for recording
bRecording = false;
nElapsedFrames = 0;
noSmooth() ;
}
// use keypress to trigger recording
void keyPressed() {
bRecording = true;
nElapsedFrames = 0;
//noLoop();
}
//for recording
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 {
percentCompleteFraction = (float) (frameCount % nFramesInLoop) / (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+1)) {
bRecording = false;
}
}
println("recording " + bRecording);
}
void renderMyDesign(float percent) {
background(backgroundColor);
sand.addParticles();
pearl.run();
sand.run();
}
Pearl Class
// Pearl class - basically a shrinking and growing circle
class Pearl {
int x;
int y;
float minRadius;
float maxRadius;
float currentRadius;
float growthFactor;
color pearlColor;
float epsilon;
ArrayList borderParticles;
Pearl(int instanceX, int instanceY) {
x = instanceX;
y = instanceY;
minRadius = 1; // could easily be a parameter
maxRadius = 170;
currentRadius = minRadius + 1;
pearlColor = (255);
growthFactor = 1.33;
epsilon = 1;
}
int getCenterX() {
return x + (int)currentRadius;
}
int getCenterY() {
return y + (int)currentRadius;
}
void run() {
update();
display();
}
void update() {
float diffToMax = abs(currentRadius - maxRadius);
float diffToMin = abs(currentRadius - minRadius);
if (diffToMax < epsilon || diffToMin < epsilon) {
growthFactor*=-1;
}
currentRadius = currentRadius + growthFactor;
}
void display() {
noStroke();
fill(pearlColor);
ellipse(x, y, currentRadius*2, currentRadius*2);
}
}
Particle Class
// A simple Particle class based of class by Daniel Shiffman
// Source: http://processing.org/examples/simpleparticlesystem.html
class Particle {
PVector location;
PVector velocity;
PVector acceleration;
float lifespan;
float pSize;
color pColor;
Particle(PVector thisLocation) {
acceleration = new PVector(random(-1, 1), random(-1, 1));
velocity = new PVector(random(-3, 3),random(-3, 3));
location = thisLocation.get();
lifespan = 10000.0;
pSize = 2;
pColor = color(30);
}
// calculate new values and redraw particle
void run() {
update();
display();
}
// Method to update location
void update() {
bounceOffWalls();
avoidPearl();
acceleration.x *= 0.9;
acceleration.y *= 0.9;
velocity.add(acceleration);
location.add(velocity);
lifespan -= 1.0;
}
// Method to make particles bounce off walls
void bounceOffWalls() {
// bounce off the walls
if (location.x < = 0 || location.x >= width) {
toggleXVelocity();
} else if (location.y < = 0 || location.y >= height){
toggleYVelocity();
}
}
void reverseVelocityDirection() {
toggleXVelocity();
toggleYVelocity();
}
void toggleXVelocity() {
velocity.x = velocity.x * -1;
}
void toggleYVelocity() {
velocity.x = velocity.x * -1;
}
// Method to avoid pearl
void avoidPearl() {
float sumRadii = pSize + pearl.currentRadius;
float sandPearlDistance = dist(getCenterX(), pearl.getCenterX(), getCenterY(), pearl.getCenterY());
if (sandPearlDistance == sumRadii) {
pColor = backgroundColor;
}
else if (sandPearlDistance < sumRadii) {
reverseVelocityDirection();
// if growth factor is positive, then particles that hit the pearl
// should disappear
if (pearl.growthFactor > 0) {
lifespan = 0.0;
}
// if the pearl is shrinking, the particles that hit should turn white
// and have a very short lifespan
else {
erodePearl();
}
}
}
void erodePearl() {
if (millis()%2 == 0) {
pSize = 0.8;
pColor = color(255);
lifespan = 10;
velocity.div(3);
}
}
// Method to display
void display() {
// lifespan determines opacity of stroke and fill
stroke(pColor, lifespan);
fill(pColor, lifespan);
ellipse(location.x, location.y, pSize, pSize);
}
// Is the particle still useful?
boolean isDead() {
if (lifespan < 0.0) {
return true;
} else {
return false;
}
}
// copy-pasted from pearl but they should probably both be subclasses
float getCenterX() {
return location.x + pSize;
}
float getCenterY() {
return location.y + pSize;
}
}
Particle System Class
// A class to describe a group of Particles by Daniel Shiffman
// Source: http://processing.org/examples/simpleparticlesystem.html
// An ArrayList is used to manage the list of Particles
class ParticleSystem {
ArrayList particles;
PVector origin;
ParticleSystem(PVector location) {
origin = location.get();
particles = new ArrayList();
}
void addParticles() {
int numParticles = (int)random(30, 50);
for (int i = 0; i < = numParticles; i++) {
addParticle();
}
}
PVector getRandomLocation() {
return new PVector(random(0,width), random(0,height));
}
void addParticle() {
PVector randomLocation = getRandomLocation();
particles.add(new Particle(randomLocation));
}
void run() {
for (int i = particles.size()-1; i >= 0; i--) {
Particle p = particles.get(i);
p.run();
if (p.isDead()) {
particles.remove(i);
}
}
}
}