/** * Starry Night * by odbol * http://www.odbol.com * * based on: * Multiple Particle Systems * by Daniel Shiffman. * * Click the mouse to generate a burst of particles * at mouse location. * * Each burst is one instance of a particle system * with Particles and CrazyParticles (a subclass of Particle) * Note use of Inheritance and Polymorphism here. * * Created 2 May 2005 */ ArrayList psystems; ArrayList origPath; PImage bg; float threshold = 300.0; //the amount of acceleration threshold (how tight the path is followed) float jitteryness = 0.9; //the amount of random acceleration added to each particle boolean crazytime; PFont fontA; int textStart; void setup() { //size(1152,864); size(480, 360); frameRate(30); colorMode(RGB,255,255,255,100); // Load the font. Fonts are located within the // main Processing directory/folder and they // must be placed within the data directory // of your sketch for them to load fontA = loadFont("ArialMS-10.vlw"); textStart = height - 14; // Set the font and its size (in units of pixels) textFont(fontA, 10); clearAll(); crazytime = false; bg = loadImage("Starry_Night-Vincent_VanGogh(480).jpg"); smooth(); } void clearAll() { psystems = new ArrayList(); origPath = new ArrayList(); background(0); } void draw() { //background(0); //background(bg); if(mousePressed) { origPath.add(new Vector3D(mouseX, mouseY)); } //drawPath(origPath); // Cycle through all particle systems, run them and delete old ones for (int i = psystems.size()-1; i >= 0; i--) { ParticleSystem psys = (ParticleSystem) psystems.get(i); psys.run(); if (psys.dead()) { psystems.remove(i); } } noStroke(); if (mouseY < textStart) { //erase old stuff first fill(0); rect(0, textStart, width, height); fill(255); text("click = start space = clear C = crazytime 1-9 = acceleration", width / 2 - 145, height - 6); } else { //erase old stuff first fill(0); rect(0, textStart, width, height); fill(0, 60, 255); text("http://www.odbol.com", width / 2 - 58, height - 6); } } void keyReleased() { if(key == 'c' || key == 'C') { crazytime = !crazytime; clearAll(); } else if (key == ' ') clearAll(); else { switch (key) { case '1': threshold = 20.0; break; case '2': threshold = 50.0; break; case '3': threshold = 70.0; break; case '4': threshold = 100.0; break; case '5': threshold = 200.0; break; case '6': threshold = 400.0; break; case '7': threshold = 600.0; break; case '8': threshold = 800.0; break; case '9': threshold = 1000.0; break; } } } //draw the path void drawPath(ArrayList path) { for (int i = 0; i < path.size() - 1; i++) { Vector3D first = (Vector3D)path.get(i); Vector3D next = (Vector3D)path.get(i+1); stroke(155); line(first.x, first.y, next.x, next.y); stroke(255); ellipse(first.x, first.y, 2,2); } } ArrayList randomizePath(ArrayList path) { float offset = random(-50,50); ArrayList newPath = new ArrayList(path.size()); //everyone starts with the same origin //newPath.add(path.get(0)); for (int i = 0; i < path.size(); i++) { //and ends at the same place //if (i + 1 == path.size()) // offset = 0; Vector3D cur = (Vector3D)path.get(i); newPath.add(new Vector3D(cur.x, cur.y + offset, 0)); } //drawPath(newPath); return newPath; } // When the mouse is pressed, add a new particle system void mouseReleased() { if (mouseY > textStart) { //load the link link("http://www.odbol.com", "_new"); } else { int pCount = 1; if (crazytime) pCount = int(random(5,25)); ParticleSystem psys = new ParticleSystem(pCount,new Vector3D(mouseX, mouseY, 0), randomizePath(origPath)); psystems.add(psys); } } // A subclass of Particle // Created 2 May 2005 class CrazyParticle extends Particle { // Just adding one new variable to a CrazyParticle // It inherits all other fields from "Particle", and we don't have to retype them! float theta; int pathIdx; float r, g, b; float distanceFromTarget; ArrayList path; //holds the path that this particle should follow // The CrazyParticle constructor can call the parent class (super class) constructor CrazyParticle(Vector3D l, ArrayList path) { // "super" means do everything from the constructor in Particle super(l); // One more line of code to deal with the new variable, theta theta = 0.0; pathIdx = 0; timer = 100000.0; this.path = path; distanceFromTarget = 0.0; } // Notice we don't have the method run() here; it is inherited from Particle // This update() method overrides the parent class update() method void update() { //timer -= 1.0; //figure out closest path point and accellerate towards it //Vector3D target = (Vector3D)path.get(pathIdx); Vector3D target = new Vector3D(mouseX, mouseY, 0); distanceFromTarget = loc.distance(loc, target); //if (pathIdx >= path.size() - 1) // timer = 0.0; //else { //if (loc.x == target.x && loc.y == target.y) // target = (Vector3D)path.get(++pathIdx); acc.setXYZ(target.sub(target, loc)); //acc.div(300.0); if (crazytime) { acc.normalize(); acc.mult(log(distanceFromTarget + 0.01) / log(10)); acc.limit(threshold / 150.0); } else { //acc.add(new Vector3D(random(-1 * jitteryness, jitteryness), random(-1 * jitteryness, jitteryness), 0)); //acc.div(distanceFromTarget + 0.01); acc.div(threshold); } //acc.div(distanceFromTarget + 0.01); //acc.mult(vel.div(vel, log(distanceFromTarget + 0.01) / log(10))); //println(distanceFromTarget + " log= " + (log(distanceFromTarget) / log(10)) + " acc: " + acc.x + ", " + acc.y); //acc.limit(2.0); //} vel.add(acc); if (!crazytime) vel.limit(5.0); loc.add(vel); //if we're getting farther from the target, move on to next one if (distanceFromTarget < loc.distance(loc, target)) if (pathIdx < path.size() - 1) pathIdx++; // Increment rotation based on horizontal velocity //float theta_vel = (vel.x * vel.magnitude()) / 10.0f; //theta += theta_vel; } // Override timer void timer() { timer -= 0.5; } // Method to display void render() { // Render the ellipse just like in a regular particle //super.render(); ellipseMode(CENTER); noStroke(); //fill(255, 0, 0, timer); Vector3D pathPoint = (Vector3D)path.get((int)random(0, path.size()-1)); int pixIdx = (int)loc.x + (int)loc.y * bg.width; if (pixIdx >= bg.pixels.length || pixIdx < 0) pixIdx = 0; //println(bg.pixels.length + " > " + pixIdx); r = red(bg.pixels[pixIdx]); g = green(bg.pixels[pixIdx]); b = blue(bg.pixels[pixIdx]); fill(r,g,b,150.0); //ellipse(loc.x,loc.y,acc.x * 10 + 9, acc.y * 10 + 9); if (crazytime) ellipse(loc.x,loc.y,9.0, 9.0); else ellipse(loc.x,loc.y,distanceFromTarget + 9.0, distanceFromTarget + 9.0); // Then add a rotating line pushMatrix(); //translate(loc.x,loc.y); //rotate(theta); stroke(255,timer); //line(0,0,25,0); //line(loc.x, loc.y, loc.x + , loc.y + acc.y); popMatrix(); } } // A simple Particle class class Particle { Vector3D loc; Vector3D vel; Vector3D acc; float r; float timer; // One constructor Particle(Vector3D a, Vector3D v, Vector3D l, float r_) { acc = a.copy(); vel = v.copy(); loc = l.copy(); r = r_; timer = 100.0; } // Another constructor (the one we are using here) Particle(Vector3D l) { acc = new Vector3D(0,0.0,0); vel = new Vector3D(random(-0.9,0.9),random(-0.9,0),0); //vel = new Vector3D(0.5,0.5,0); loc = l.copy(); r = 10.0; timer = 100.0; } void run() { update(); render(); } // Method to update location void update() { vel.add(acc); loc.add(vel); timer -= 1.0; } // Method to display void render() { ellipseMode(CENTER); noStroke(); fill(255,timer); ellipse(loc.x,loc.y,r,r); } // Is the particle still useful? boolean dead() { if (timer <= 0.0) { return true; } else { return false; } } } // A class to describe a group of Particles // An ArrayList is used to manage the list of Particles class ParticleSystem { ArrayList particles; // An arraylist for all the particles Vector3D origin; // An origin point for where particles are birthed ArrayList path; //The path that the particles will follow. ParticleSystem(int num, Vector3D v, ArrayList particlePath) { particles = new ArrayList(); // Initialize the arraylist origin = v.copy(); // Store the origin point path = particlePath; for (int i = 0; i < num; i++) { particles.add(new CrazyParticle(origin, path)); // Add "num" amount of particles to the arraylist } } void run() { // Cycle through the ArrayList backwards b/c we are deleting for (int i = particles.size()-1; i >= 0; i--) { Particle p = (Particle) particles.get(i); p.run(); if (p.dead()) { particles.remove(i); } } } void addParticle() { particles.add(new Particle(origin)); } void addParticle(Particle p) { particles.add(p); } // A method to test if the particle system still has particles boolean dead() { if (particles.isEmpty()) { return true; } else { return false; } } } // Simple Vector3D Class public class Vector3D extends Object{ public float x; public float y; public float z; Vector3D(float x_, float y_, float z_) { x = x_; y = y_; z = z_; } Vector3D(float x_, float y_) { x = x_; y = y_; z = 0f; } Vector3D() { x = 0f; y = 0f; z = 0f; } void setX(float x_) { x = x_; } void setY(float y_) { y = y_; } void setZ(float z_) { z = z_; } void setXY(float x_, float y_) { x = x_; y = y_; } void setXYZ(float x_, float y_, float z_) { x = x_; y = y_; z = z_; } void setXYZ(Vector3D v) { x = v.x; y = v.y; z = v.z; } public float magnitude() { return (float) Math.sqrt(x*x + y*y + z*z); } public Vector3D copy() { return new Vector3D(x,y,z); } public Vector3D copy(Vector3D v) { return new Vector3D(v.x, v.y,v.z); } public void add(Vector3D v) { x += v.x; y += v.y; z += v.z; } public void sub(Vector3D v) { x -= v.x; y -= v.y; z -= v.z; } public void mult(float n) { x *= n; y *= n; z *= n; } public void div(float n) { x /= n; y /= n; z /= n; } public void normalize() { float m = magnitude(); if (m > 0) { div(m); } } public void limit(float max) { if (magnitude() > max) { normalize(); mult(max); } } public float heading2D() { float angle = (float) Math.atan2(-y, x); return -1*angle; } public Vector3D add(Vector3D v1, Vector3D v2) { Vector3D v = new Vector3D(v1.x + v2.x,v1.y + v2.y, v1.z + v2.z); return v; } public Vector3D sub(Vector3D v1, Vector3D v2) { Vector3D v = new Vector3D(v1.x - v2.x,v1.y - v2.y,v1.z - v2.z); return v; } public Vector3D div(Vector3D v1, float n) { Vector3D v = new Vector3D(v1.x/n,v1.y/n,v1.z/n); return v; } public Vector3D mult(Vector3D v1, float n) { Vector3D v = new Vector3D(v1.x*n,v1.y*n,v1.z*n); return v; } public float distance (Vector3D v1, Vector3D v2) { float dx = v1.x - v2.x; float dy = v1.y - v2.y; float dz = v1.z - v2.z; return (float) Math.sqrt(dx*dx + dy*dy + dz*dz); } }