Skip to content

Instantly share code, notes, and snippets.

@Craigson
Created February 7, 2015 16:30
Show Gist options
  • Select an option

  • Save Craigson/043cd666542f9a5b5dfd to your computer and use it in GitHub Desktop.

Select an option

Save Craigson/043cd666542f9a5b5dfd to your computer and use it in GitHub Desktop.
Snowflakes fall and experience a drag force when they pass over the background PImage
class Flake {
PVector loc, vel, acc; //create Pvectors for location, velocity and acceleration
float mass, radius, lifespan;
float x, xoff; //variables for perlin noise
boolean isTrapped = false; //boolean variable for determining whether the flake is over a letter
float c = 0.0001; //this is the drag co-efficient of the word
float theta, dTheta; //variables for rotating the polygon
Flake() {
loc = new PVector(random(-100, width), -10); //initialise the flake above the window
mass = random(4, 10); //randomly assign a mass to the flake
radius = mass*0.4 ; //the radius is a function of the mass
acc = new PVector();
vel = new PVector();
lifespan = 2000; //set the lifespan of the flake, it will be removed when this runs out
xoff = random(-.01, 0.1); //random offset for incrementing perlin noise
theta = 0.0; //the angle of rotation
dTheta = random (-0.07, 0.07); //set a random amount by which to increment theta and thus rotate the polygon
}
void run() {
update();
display();
lifespan -= 0.1; //reduce the lifespan of the flake every frame
}
void update() {
if (isTrapped == true) { //if the flake is over a letter, apply a drag force
applyDrag();
acc = new PVector(0, 0);
vel = new PVector(0, 0);
} else {
loc.add(vel);
vel.add(acc);
}
acc.mult(0); //reset the acceleration every frame
//this portion of code gives the snowflakes the subtle side-to-side motion
//that gives them a more natural look, as if they were very light objects
//experiencing air resistance as they fall to earth
//if the random float r is less than 0.05, the increment is small, meaning a smaller movement horizontally
float q = random(1);
if (q < 0.05) {
xoff += 0.1;
} else {
xoff += 0.005;
}
//if the particle is free to move, ie not over the image, execute the following code
if (isTrapped ==false) {
loc.x += map(noise(xoff), 0, 1, -0.5, 0.5); //adjust the x-location of the particle according to the noise value
}
}
//if the flake is "dead" set the boolean isMelted to true so that the system can remove the flake
boolean isMelted() {
if (lifespan < 0.0) {
return true;
} else {
return false;
}
}
void display() {
noStroke();
fill(255, 245);
// ellipse(loc.x, loc.y, radius, radius);
pushMatrix();
translate(loc.x, loc.y); //move the polygon to the new x-y location
rotate(theta); //rotate the polygon by angle theta
polygon(0, 0, radius, 5); //draw the pentagon
popMatrix();
//if the pentagon is free to move, ie not over the PImage
//increment theta by dTheta
if (isTrapped == false) {
theta += dTheta;
}
}
void applyForce(PVector force) {
PVector f = force.get(); //make a copy of the force
f.div(mass); //divide by the mass, this ensures the acceleration is affected by the objects mass
acc.add(f); //add the force to the acceleration
}
PVector applyDrag() {
float speed = vel.mag(); //speed is the scalar value of the mover's velocity
float dragMag = c*speed*speed; //the magnitude of the drag force is equivalent to the patch's
//coefficient of friction multiplied by the square of the movers speed
PVector drag = vel.get(); //get a copy of the movers direction
drag.normalize(); //normalize to get the unit vector
drag.mult(-1); //reverse the direction
drag.mult(dragMag); //multiply the unit vector by the magnitude of the force
return drag; //apply the new drag force to 'this' mover
}
//create a polygon for drawing the snowflake as a pentagon
void polygon(float x, float y, float radius, int npoints) {
//the angle at which to draw the vertices is determined
//by dividing 2PI (360 degrees) by the number of vertices
float angle = TWO_PI / npoints;
beginShape();
for (float a = 0; a < TWO_PI; a += angle) {
float sx = x + cos(a) * radius;
float sy = y + sin(a) * radius;
vertex(sx, sy);
}
endShape(CLOSE);
}
}
class FlakeSystem {
ArrayList <Flake> snowflakes; //arraylist of flakes
FlakeSystem() {
snowflakes = new ArrayList<Flake>();
}
//for every flake, apply the force divided by the mass of the object
//this ensures different sized flakes are affected differently by forces applied to them
void applyForce(PVector force) {
for (Flake f : snowflakes) {
PVector _f = PVector.div(force, f.mass); //Pass the vector by copy to leave the original unaltered
f.applyForce(_f);
}
}
//for each flake, multiply by the mass to cancel out the acceleration due to gravity
//this ensurse each flake falls at the same rate
void applyGravity(PVector force) {
for (Flake f : snowflakes) {
PVector gravity = force.get(); //create a copy of the vector
gravity.mult(f.mass); //multiply the force by the objects mass (this cancels out mass)
f.applyForce(gravity); //apply the gravity force
}
}
void run() {
//Create an iterator to go through the array list and remove "dead" particles
Iterator<Flake> itty = snowflakes.iterator(); //a new iterator
//while the iterator is not at the end, execute the following code
while (itty.hasNext ()) {
Flake f = itty.next();
f.run();
if (f.isMelted()) {
//if the boolean isMelted (once at the end of its lifespan)
//is set to true, remove the particle
itty.remove();
}
}
}
//this method checks the location of each flake, if the flake is over a part of the PImage
//that is black (color -16777216)
void checkLocation(PImage img1) {
for (Flake f : snowflakes) {
//variable "pixelColour" is set according to the corresponding pixel
//colour of the PImage at the flake f's x-and-y-locations
color pixelColour = img1.get(int(f.loc.x), int(f.loc.y));
// println(pixelColour); <- this was used to determine the value
//create a float variable r and set it in a range between 0 and 1
//5% of the time the flake's local boolean value for isTrapped is set to
//true, this allows certain flakes to float past the image
float r = random(1);
if (pixelColour == -16777216 && r < 0.1) {
f.isTrapped = true;
// println(f.isTrapped);
} else if (f.loc.y > height -5){
f.isTrapped = true;
}
}
}
//there is a checkLocation method inside the windChannel class
//that determines if the flake is inside, if it is, the
//applyMethod() function is executed
void getWind(WindChannel w){
for (Flake f : snowflakes){
f.applyForce(w.applyWind(f));
}
}
//this method adds a new flake to the arraylist when it's called
void addFlake() {
snowflakes.add(new Flake());
}
}
PImage img; //create the image object for trapping snow
FlakeSystem fs; //new particle system to contral snowflakes
import java.util.Iterator;
PVector wind = new PVector(0.04,0); //create a wind vector to apply horizontal wind
WindChannel wc; //create a new random wind generator
Timer timer = new Timer(150); //set the timer to add particles every 150ms
void setup (){
noCursor(); //hide the mouse cursor
size(1200,675);
fs = new FlakeSystem(); //initialise the particle system
img = loadImage("snow(1).png"); //load the image into the system
timer.start(); //start the timer
wc = new WindChannel(); //initialise the random wind object3
}
void draw(){
PVector gravity = new PVector(0,0.003); //create a universal gravity force
background(20);
//comment out the image so that it doesn't get drawn to the screen
//image(img,0,0);
//when the timer expires, add a flake, then restart the timer
if (timer.isFinished()){
fs.addFlake();
timer.start();
}
wc.move(); //execute the windchannel's move method
wc.display(); //display the windchannel
fs.getWind(wc); //apply the getwind() method to the particle system, passing in the windchannel object as a parameter
fs.checkLocation(img); //pass the PImage object into the checkLocation method to determine if the flake is over a letter
fs.applyForce(wind); //apply the horizontal wind force
fs.applyGravity(gravity); //apply the universal gravity force
fs.run(); //execute all the methods in the FlakeSystem class
//use these for testing performance
println(frameRate + " " + fs.snowflakes.size());
//println(frameRate + " " + fs.snowflakes.size() + wc.windStrength);
}
class Timer {
int savedTime;
boolean running = false;
int totalTime;
Timer(int tempTotalTime){
totalTime = tempTotalTime;
}
void start(){
running = true;
savedTime = millis();
}
boolean isFinished(){
int passedTime = millis() - savedTime;
if (running && passedTime > totalTime) {
running = false;
return true;
}else{
return false;
}
} //end of isFinished
}
//this class creates a block of wind that moves around
//the window, when objects pass into the object, a force
//(windStrength) is applied to said objects
class WindChannel {
float w,h; //the width and height of windchannel
PVector loc, vel, acc, noff; //variables for controlling movement
PVector windStrength; //the force to be applied
WindChannel(){
loc = new PVector(width/2, height/2);
vel = new PVector();
acc = new PVector();
//assign a random PVector as the strenght of the wind
windStrength = new PVector(random(-0.04, 0.004), random(-0.03,0));
w = width/4;
h = height/6;
noff = new PVector(random(1000), random(1000)); //value used for incrementing noise
}
void display(){
rectMode(CENTER);
// fill(255,20);
noFill();
rect(loc.x,loc.y,w,h);
}
PVector applyWind(Flake f){
PVector pvec = new PVector(0,0); //create a PVector with a value of (0,0)
//if the flake is within the windChannel, apply the force of
//windspeed, else apply the null force
if (f.loc.x > loc.x - w/2 && f.loc.x < loc.x + w/2 && f.loc.y > loc.y - h/2 && f.loc.y < loc.y + h/2){
return windStrength;
} else {
return pvec;
}
}
//update the windchannel's position using noise
void move(){
acc.x = map(noise(noff.x), 0, 1, -2, 2);
acc.y = map(noise(noff.y), 0, 1, -2, 2);
acc.mult(0.1);
noff.add(0.05, 0.05, 0);
vel.add(acc);
vel.limit(1);
loc.add(vel);
//constrain the windchannel's location within the window
loc.x = constrain(loc.x,0,width);
loc.y = constrain(loc.y,0,height);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment