Recently, the Denver Theater District launched Night Lights Denver, a permanent projection mapping project on the iconic Daniels & Fisher Tower. The project aims to become “‘the people’s projector,’ encouraging Denver to participate and contribute – including the international, national and locally-based artists we pay for commissioned artworks, schools and people walking through the district.”
To accompany the upcoming holiday festivities, the parade of lights, and other winter events, they solicited an RFP for winter or holiday-themed content. I thought it could be a cool opportunity to apply some of the generative coding skills I’ve been working on and submitted a proposal to make it snow on the tower. My project was accepted!
To make the piece, I worked in Processing to create a particle system that resembled snowflakes. Each snowflake was assigned a random size using a gaussian randomizer. By applying a gravity vector to each snowflake based on its size, the large flakes fall faster than the smaller ones, giving the illusion of depth. Horizontal movement is added using a SIN function in relationship to the width of the building. Finally, you can control the total number of snowflakes, and each flake is recycled once it crossed the bottom of the screen.
Check out a mockup of the project here.
Documentation of the projection coming soon!
Processing Code:
// Original Code based off of Coding train challenge #88, by Daniel Shiffman
// thecodingtrain.com/CodingChallenges/088-snowfall.html
// Modied by Clayton Kenney, Nov 2019.
import com.hamoid.*; //Video export library.
ArrayList<Snowflake> snow; //array that holds the snowflakes
PVector gravity; //gravity global variable
float angle; //angle global variable
int t; //time global variable
VideoExport videoExport;
int frameRate = 30;
boolean render = false;
float xOff;
//generates random number that skews smaller by comapring random numbers. add one to ensure flakes aren't too small
float getRandomSize() {
float r3 = randomGaussian() * 3 + 5;
return constrain(abs(r3), 3, 100);
}
//snowflake constructor
class Snowflake {
PVector pos;
PVector vel;
PVector acc;
float r;
float radius;
float initialAngle;
Snowflake(float x1, float y1) {
float x = x1; //starting point of flake based on width
float y = y1; //generate flakes offscreen so they are falling
float xv = random(-.5, .5); //assign random horizontal vector on creation
pos = new PVector(x, y); //position vector
vel = new PVector(xv, 0); //initial velocity vector
acc = new PVector(); //acceleration vector
r = getRandomSize(); //snowflake size based on above rando algo
radius = sqrt(random(pow(width / 2, 2)));
initialAngle = random(0, 2 * PI); //generate random starting angle between 0 & 2PI
xOff = 0;
}
void applyForce(PVector force){
//faux parallax effect
PVector f = force.copy();
f.mult(r); //gravity acts more on 'bigger' aka heavier particles
acc.add(f); //gravity force to accelerator
}
void randomize() {
float x = random(width);
float y = random(-100, -10);
float xv = random(0); //assign random horizontal vector on creation
pos = new PVector(x, y);
vel = new PVector(xv, 0); //initial velocity vector
acc = new PVector(); //acceleration vector
initialAngle = random(0, 2 * PI); //generate random starting angle between 0 & 2PI
r = getRandomSize(); //snowflake size based on above rando algo
}
void update() {
vel.add(acc);
vel.limit(r * .3); //gravity limit .2-.5
pos.add(vel);
acc.mult(0);
float w = 0.003; // angular speed
float angle = w * t + initialAngle;
pos.x = width/ 2 + radius * sin(angle);
if (pos.y > height + r) {
randomize();
}
}
void render() {
stroke(255);
strokeWeight(r);
point(pos.x, pos.y);
}
}
void setup() {
//const canvas = createCanvas(1080, 3438); //canvas is 1/4 scale of tower, set to canvas variable
size(1080, 3438); //1080x3438 full res, 270x860 1/4 scale
if (render) {
videoExport = new VideoExport(this, "3000.mp4");
videoExport.setQuality(100, 128);
videoExport.setFrameRate(frameRate);
}
frameRate(frameRate);
gravity = new PVector(0, 1); //define gravity
snow = new ArrayList<Snowflake>();
for (int i=0; i < 3000; i++) {
float x = random(width);
float y = random(height);
snow.add(new Snowflake(x, y));
}
//no export if render is set to false
if (render) {
videoExport.startMovie();
}
}
void draw(){
background(1); //black for test
t = frameCount; //set the time
//Make Snow Flakes!!
for(int i =0; i< snow.size(); i++){
Snowflake flake = snow.get(i);
flake.applyForce(gravity);
flake.update();
flake.render();
}
//Renderer
if (render) {
videoExport.saveFrame();
}
}
void keyPressed() {
if (key == 'q') {
videoExport.endMovie();
exit();
}
}