3000 SNOWFLAKES

Recently, the Denver Theater District launched Night Lights Denvera 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();
  }
}