xxxxxxxxxx
// Dan Shiffman "The Nature of Code" Ch. 6
// https://www.youtube.com/watch?v=IoKfQrlQ7rA
// Basic flocking study:
// 3 species of boids, red, blue and white.
// Boids are aware of, and try not to collide with,
// all other boids (360 degree view),
// but only attempt to flock with their own species.
// Mouse click to reset
// boid, ArrayList, PVector, color, rect, text, cos, sin, random, map, vertex
ArrayList<Boid> allBoids;
color blue, red, white, setCol;
int numBoids = 150;
void setup() {
size(1200, 650);
background(0);
noStroke();
blue = color(#0D22FC);
red = color(#FC0D29);
setCol = white = color(#FFFFFF);
makeBoids();
}
void draw() {
fill(0, 20);//19
rect(0, 0, width, height);
fill(255, 5);
textSize(20);
text("FPS:", 50, height-50);
text(frameRate, 100, height-50);
for (Boid b : allBoids) {
b.run();
}
}
// functions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
void makeBoids() {
allBoids = new ArrayList<Boid>();
for (int i = 0; i < numBoids; i++) {
if (setCol == red) setCol = blue;
else if (setCol == blue) setCol = white;
else if (setCol == white) setCol = red;
float angle = random(TWO_PI);
float x = width/2 + cos(angle) * 200;
float y = height/2 + sin(angle) * 200;
allBoids.add(new Boid(x, y, setCol));
}
}
void mousePressed() {
makeBoids();
}
// class %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
class Boid {
PVector pos, vel, accel;
float r, maxforce, maxspeed, theta, d = 150;
color col;
Boid(float x, float y, color cin) {
col = cin;
accel = new PVector(0, 0);
vel = new PVector(random(-1, 1), random(-1, 1));
pos = new PVector(x, y);
r = 5.0;
maxspeed = 3;
maxforce = 0.03;
}
void run() {
flock();
update();
boundaries();
// borders();
render();
}
void update() {
vel.add(accel);
vel.limit(maxspeed);
pos.add(vel);
accel.mult(0);
}
void flock() {
PVector sep = separate();
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
PVector ali = align(red);
if (ali.x == 0 && ali.y == 0) ali = align(blue); // no red so check blue
if (ali.x == 0 && ali.y == 0) ali = align(white); // no red or blue so check white
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
PVector coh = cohesion(red);
if (coh.x == 0 && coh.y == 0) coh = cohesion(blue); // no red so check blue
if (coh.x == 0 && coh.y == 0) coh = cohesion(white); // no red or blue so check white
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
accel.add(sep);
accel.add(ali);
accel.add(coh);
}
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// Cohesion: Steer towards flockmates that have moved too far away.
// Collect the average pos of all nearby boids of same species. This becomes "desired".
// Steer towards desired.
PVector cohesion (color colIn) {
float neighbordist = 60;
PVector sum = new PVector(0, 0); // Start with empty vector to accumulate all pos's
int count = 0;
for (Boid other : allBoids) {
float d = PVector.dist(pos, other.pos);
if ((d > 0) && (d < neighbordist) && (other.col == colIn) && (this.col == colIn)) {
sum.add(other.pos); // Add pos
count++;
}
}
if (count > 0) {
sum.div(count); // average
sum.sub(pos); // vec pointing from current pos to "desired" pos
sum.normalize();
sum.mult(maxspeed);
// Steering = Desired - vel
sum.sub(vel);
float setMax = map(sum.mag(), 0, 5, 0, maxforce);
sum.limit(setMax);
return sum;
} else {
return new PVector(0, 0);
}
}
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// Separate: Steer away from flockmates, of all species, that are too close.
// Calc vector pointing away from each nearby flockmate.
// The average of these vectors becomes "desired".
PVector separate () {
float sepDist = 30;// r*6;
PVector sum = new PVector(0, 0); // ends up as "desired"
int count = 0;
float dAv = 0, dTotal = 0;
for (Boid other : allBoids) {
float d = PVector.dist(pos, other.pos);
// d>0 stops self-checking but allows some boids to piggy-back from time to time
if ((d > 0) && (d < sepDist)) {
dTotal += d;
PVector diff = PVector.sub(pos, other.pos); // points away
diff.normalize();
sum.add(diff);
count++;
}
}
// Average == divide by how many
if (count > 0) {
sum.div((float)count);
dAv = dTotal/float(count);
}
if (sum.mag() > 0) {
// force has inverse relatioship to average distance
// if flockmates are closer, use more force, further away, use less
float setMaxForce = map(dAv, 0, sepDist, .1, 0);
// Steering = Desired - vel
sum.normalize();
sum.mult(maxspeed);
sum.sub(vel); // sum is "desired"
sum.limit(setMaxForce);
//sum.setMag(setMaxForce);//not js mode
}
return sum;
}
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// Align: Steer towards average heading (not pos) of nearby flockmates of same species.
// Collect heading vector for each nearby flockmate.
// the average of these vecs becomes "desired".
PVector align (color colIn) {
float neighbordist = 60;
PVector sum = new PVector(0, 0);
int count = 0;
for (Boid other : allBoids) {
float d = PVector.dist(pos, other.pos);
if ((d > 0) && (d < neighbordist) && (other.col == colIn) && (this.col == colIn)) {
sum.add(other.vel);// total of headings of each local flockmate
count++;
}
}
if (count > 0) {
sum.div((float)count); // average
// Steering = Desired - vel
sum.normalize();
sum.mult(maxspeed);
sum.sub(vel);
float setMax = map(sum.mag(), 0, 5, 0, .03);
sum.limit(setMax);
return sum;
} else return new PVector(0, 0);
}
// Wraparound
void borders() {
if (pos.x < -r) pos.x = width+r;
if (pos.y < -r) pos.y = height+r;
if (pos.x > width+r) pos.x = -r;
if (pos.y > height+r) pos.y = -r;
}
void boundaries() {
PVector desired = null;
if (pos.x < d) {
desired = new PVector(maxspeed, vel.y);
} else if (pos.x > width -d) {
desired = new PVector(-maxspeed, vel.y);
}
if (pos.y < d) {
desired = new PVector(vel.x, maxspeed);
} else if (pos.y > height-d) {
desired = new PVector(vel.x, -maxspeed);
}
if (desired != null) {
desired.normalize();
desired.mult(maxspeed);
desired.sub(vel);
desired.limit(.01);
accel.add(desired);
}
}
void render() {
theta = vel.heading2D() + PI/2;
fill(col);
pushMatrix();
translate(pos.x, pos.y);
rotate(theta);
beginShape();
vertex(0, -r*2);
vertex(-r*1.5, r*1.25);
vertex(0, 0);
vertex(r*1.5, r*1.25);
endShape(CLOSE);
popMatrix();
}
}