Created
February 7, 2015 16:30
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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()); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //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