BoidFlock flock; //import processing.video.*; //MovieMaker mm; //import processing.pdf.*; //boolean saveOneFrame = false; void setup(){ size(640, 480); flock = new BoidFlock(); for(int i = 0; i < 100; i++){ flock.addBoid(random(width), random(height)); //size(320,240); //mm = new MovieMaker(this, 640, 480, "boid.mov", 30, MovieMaker.H263, MovieMaker.HIGH); //background(0); } } void draw(){ fill(0, 10); rect(0, 0, width, height); flock.verlet(); flock.group(); flock.avoid(); flock.migrate(); flock.goal(mouseX, mouseY); flock.drawParticles(); flock.drawParticles2(); //ellipse(mouseX, mouseY, .5, .5); //mm.addFrame(); //if(saveOneFrame == true) { //beginRecord(PDF, "BOID-" + mouseX+"_" + mouseY + ".pdf"); } //if(saveOneFrame == true) { //endRecord(); //saveOneFrame = false; //} //} //void mousePressed(){ //mm.finish(); //} // Flocking Particle // - hacky implementation so far, but that's mainly to get it to work with my Physics class // by dickering with the general rule speeds you'll get the sort of behaviour that will look cool class Boid extends Particle{ float centering = 0.01; float avoidance = 0.05; float goalSpeed = 0.08; int id; BoidFlock p; // Constructor Boid(float x, float y, BoidFlock p){ super(x, y, p); this.p = p; } // Keep the boid moving towards the center of the group void group(){ // center measurements float vx = p.center.x-x; float vy = p.center.y-y; float invLen = Line.invSqrt(vx * vx + vy * vy); float dx = vx * invLen; float dy = vy * invLen; addVelocity(dx*centering, dy*centering); } // Avoid other boids void avoid(){ Point move = new Point(0, 0); for(int i = 0; i < p.p.size(); i++){ if(i == id) continue; Boid temp = (Boid)p.p.get(i); float vx = x - temp.x; float vy = y - temp.y; float invLen = Line.invSqrt(vx * vx + vy * vy); if(invLen > avoidance){ float dx = vx * invLen; float dy = vy * invLen; addVelocity(dx*avoidance, dy*avoidance); } } } // Fly toward a given goal void goal(float tx, float ty){ // goal measurements float vx = tx-x; float vy = ty-y; float invLen = Line.invSqrt(vx * vx + vy * vy); float dx = vx * invLen; float dy = vy * invLen; addVelocity(dx*goalSpeed, dy*goalSpeed); } } // Manager object for a group of Boid particles class BoidFlock extends Physics{ Point center, migration; float migrationSpeed = 0.005; int count; // Constructor BoidFlock(){ super(); center = new Point(); migration = new Point(); count = 0; } // Add a Boid - in lieu of addParticle so it gets the right object Boid addBoid(float x, float y){ Boid temp = new Boid(x, y, this); temp.id = count; count++; p.add(temp); return temp; } // Move all the boids to their known center void group(){ updateCenter(); for(int i = 0; i < p.size(); i++){ Boid temp = (Boid)p.get(i); temp.group(); } } // Make all the boids avoid each other void avoid(){ for(int i = 0; i < p.size(); i++){ Boid temp = (Boid)p.get(i); temp.avoid(); } } // Make all the boids fly in the same direction void migrate(){ updateMigration(); for(int i = 0; i < p.size(); i++){ Boid temp = (Boid)p.get(i); temp.addVelocity(migration.x * migrationSpeed, migration.y * migrationSpeed); } } // Give all the boids a common goal to fly towards void goal(float x, float y){ for(int i = 0; i < p.size(); i++){ Boid temp = (Boid)p.get(i); temp.goal(x, y); } } // Update the average vector they are all flying in void updateMigration(){ float x = 0; float y = 0; for(int i = 0; i < p.size(); i++){ Boid temp = (Boid)p.get(i); x += temp.x - temp.px; y += temp.y - temp.py; } migration.x = x / p.size(); migration.y = y / p.size(); } // Update the current center of the group void updateCenter(){ float x = 0; float y = 0; for(int i = 0; i < p.size(); i++){ Boid temp = (Boid)p.get(i); x += temp.x; y += temp.y; } center.x = x / p.size(); center.y = y / p.size(); } } // Simple line math object // I just find all this vector stuff invaluable - one call to updateLine and I know where I'm facing static class Line{ Point a, b; float vx, vy, dx, dy, lx, ly, rx, ry, len, invLen, theta; // Constructor Line(Point a, Point b){ this.a = a; this.b = b; updateLine(); } // Refresh values of normals void updateLine(){ vx = b.x - a.x; vy = b.y - a.y; invLen = invSqrt(vx * vx + vy * vy); //len = 1.0f/invLen; //len = sqrt(vx * vx + vy * vy); if(len > 0){ //dx = vx * invLen; //dy = vy * invLen; dx = vx/len; dy = vy/len; } else { dx = 0.0f; dy = 0.0f; } rx = -dy; ry = dx; lx = dy; ly = -dx; } // Spin line around the axis of point a void rotateA(float angle){ angle += getTheta(); theta = angle; b.x = a.x + cos(angle) * len; b.y = a.y + sin(angle) * len; updateLine(); } // Spin line around the axis of point b void rotateB(float angle){ angle += getTheta(); theta = angle; a.x = b.x + cos(angle) * len; a.y = b.y + sin(angle) * len; updateLine(); } // Get the angle of the line float getTheta(){ theta = atan2(vy, vx); return theta; } // Return a point between a and b Point lerp(float n){ return(new Point((a.x + b.x) * n, (a.y + b.y) * n)); } // Some methods I find easier to read as static: // Get the dot product from the left hand normal static float perP(Line a, Line b){ return a.vx * b.vy - a.vy * b.vx; } // Get a normalised left hand dot product static float normalPerP(Line a, Line b){ return a.dx * b.dy - a.dy * b.dx; } // Get the Point at which two lines intersect static Point intersectionPoint(Line a, Line b){ Line c = new Line(new Point(0,0), new Point(b.a.x-a.a.x, b.a.y-a.a.y)); float t = perP(c, b)/perP(a, b); return new Point(a.a.x+a.vx*t, a.a.y+a.vy*t); } // Do two lines actually cross each other? static boolean intersects(Line a, Line b){ Line c = new Line(new Point(0,0), new Point(b.a.x-a.a.x, b.a.y-a.a.y)); float perP0 = perP(c, b); float perP1 = perP(a, b); float t0 = perP0/perP1; float t1 = perP1/perP0; return t0 >= 0 && t0 <= 1 && t1 >= 0 && t1 <= 1; } // Return the dot product of two vectors (lines) static float dot(Line a, Line b){ return a.vx * b.vx + a.vy * b.vy; } // Original C code by Chris Lomont static float invSqrt(float x){ float xhalf = 0.5f*x; int i = Float.floatToIntBits(x); // get bits for floating value i = 0x5f375a86- (i>>1); // gives initial guess y0 x = Float.intBitsToFloat(i); // convert bits back to float x = x*(6f-xhalf*x*x); // Newton step, repeating increases accuracy return x; } } // A particle moved by Verlet Integration - that means velocity is inferred from it's previous and current position // It requires some kind of Physics system to notify it of the global damping and gravity - hence the registration in construction // Generally particles should be spawned with Physics.addParticle class Particle extends Point{ float px, py, tempX, tempY, ix, iy; Physics p; // Constructor Particle(float x, float y, Physics p){ super(x, y); px = tempX = ix = x; py = tempY = iy = y; this.p = p; } // Movement is executed by this method void verlet(){ tempX = x; tempY = y; x += p.damping * (x - px) + p.gravityX; y += p.damping * (y - py) + p.gravityY; px = tempX; py = tempY; } // Fix the particle to a given point and kill it's velocity void setPosition(float x, float y){ this.x = px = tempX = ix = x; this.y = py = tempY = iy = y; } // Fix the particle to it's initialisation point void pin(){ x = px = tempX = ix; y = py = tempY = iy; } // Get a Line describing the particle's movement Line getLine(){ return new Line(new Point(px, py), new Point(x, y)); } // Get the distance moved in the last frame float speed(){ return sqrt((x-px)*(x-px)+(y-py)*(y-py)); } // Add speed to the particle by displacing it's last location void addVelocity(float x, float y){ px -= x; py -= y; } // Provide a copy of the particle with matching current behaviour and add it to the physics system Particle copy(){ Particle temp = p.addParticle(x, y); temp.px = px; temp.py = py; temp.ix = ix; temp.iy = iy; return temp; } } // Manager object for particles moved with Verlet Integration methods // Methods are kept separate for optimisation // Call verlet(), then springs(), then attractions(), then pin particles, then constrain any springs, then manage collisions // this order is usually fairly stable - instability explosions can happen but this ain't RK4 so live with it - plus check for typos, they are a common cause of instability class Physics{ float gravityX, gravityY, damping; ArrayList p; ArrayList s; ArrayList g; // Constructor Physics(){ p = new ArrayList(); s = new ArrayList(); g = new ArrayList(); damping = 0.99f; gravityX = 0.0f; gravityY = 0.0f; } // Move particles void verlet(){ for(int i = 0; i < p.size(); i++){ Particle temp = (Particle)p.get(i); temp.verlet(); } } // Update springs void springs(){ for(int i = 0; i < s.size(); i++){ Spring temp = (Spring)s.get(i); temp.updateSpring(); } } // Add a particle to the system and return a reference Particle addParticle(float x, float y){ Particle temp = new Particle(x, y, this); p.add(temp); return temp; } // Add a spring to the system and return a reference Spring addSpring(Point a, Point b){ Spring temp = new Spring(a, b); s.add(temp); return temp; } // Debug drawings void drawParticles(){ for(int i = 0; i < p.size(); i++){ Particle temp = (Particle)p.get(i); stroke(211, 211, 211); strokeWeight(.5); smooth(); line(mouseX, mouseY, temp.x, temp.y); } } void drawParticles2(){ for(int i = 0; i < p.size(); i++){ Particle temp = (Particle)p.get(i); stroke(255); //stroke(27, 27, 27); //fill(97, 97, 97); fill(255, 255, 255, 15); smooth(); ellipse(temp.x, temp.y, 15, 15); } } void drawSprings(){ for(int i = 0; i < s.size(); i++){ Spring temp = (Spring)s.get(i); line(temp.a.x, temp.a.y, temp.b.x, temp.b.y); } } } // Basic point object - makes the Line and Spring class independant of the Particle object static class Point{ float x, y; // Constructor Point(float x, float y){ this.x = x; this.y = y; } Point(){ this(0.0f, 0.0f); } // Set a new position void setPosition(float x, float y){ this.x = x; this.y = y; } // Copy this object Point copy(Point a){ return new Point(a.x, a.y); } // Subtract one position from another void minus(Point o){ x -= o.x; y -= o.y; } // Add one position to another void plus(Point o){ x += o.x; y += o.y; } // Scale a position void mul(float n){ x *= n; y *= n; } // Some methods I find easier to read as static // Get the distance between two points static float dist(Point a, Point b){ return sqrt((b.x - a.x) * (b.x - a.x) + (b.y - a.y) * (b.y - a.y)); } // Get a Point between Point a and b static Point lerp(Point a, Point b, float n){ return(new Point((a.x + b.x) * n, (a.y + b.y) * n)); } } // Simple spring class - fairly unrealistic but gets the job done quickly // you can modify the stiffness but a chain of springs with a stiffness above 0.5 gets unstable class Spring extends Line{ float restLength; float stiffness; Point moveBy; // Constructor Spring(Point a, Point b){ super(a, b); stiffness = 0.2f; restLength = len; moveBy = new Point(0.0f, 0.0f); } // Move points to new positions void updateSpring(){ vx = b.x - a.x; vy = b.y - a.y; len = sqrt(vx * vx + vy * vy); float diff = 0.0f; if(len > 0.0f){ diff = (len - restLength) / len; } float mul = diff * stiffness; moveBy.x = -vx*mul; moveBy.y = -vy*mul; a.x -= moveBy.x; a.y -= moveBy.y; b.x += moveBy.x; b.y += moveBy.y; updateLine(); } // These two constrain methods I developed to deal with the uncontrollable length of the springs // The spring will lax out and then it will look like an inner cord snaps to full length - not pretty but it's a good enough workaround void constrainFromA(float minLength, float maxLength){ updateLine(); if(len < minLength){ b.x = a.x + minLength * dx; b.y = a.x + minLength * dy; updateLine(); } else if(len > maxLength){ b.x = a.x + maxLength * dx; b.y = a.y + maxLength * dy; updateLine(); } } void constrainFromB(float minLength, float maxLength){ updateLine(); if(len < minLength){ a.x = b.x + minLength * dx; a.y = b.x + minLength * dy; updateLine(); } else if(len > maxLength){ a.x = b.x + maxLength * dx; a.y = b.y + maxLength * dy; updateLine(); } } } // void mousePressed() { //saveOneFrame = true; //saveFrame("BOID.jpg"); //}