Click to create a new Boid! Adjust the sliders to control the three flocking parameters.
The showcase player uses a modified version of Processing.js in combination with jsweet to let students program their apps in Java code while still allowing for browser support.
Content created by students is scaled to fit the showcase frame while maintaining aspect ratio and cursor position. This is why some projects may appear blurry in fullscreen, or why some small details may not be visible on a small screen
<iframe width='500px' height='400px' src='https://nest.ktbyte.com/nest#387801' allowfullscreen></iframe>
/* Flocking program inspired by the Vicsek Model References: https://github.com/kakehi/flocking-system-processing/blob/master/Flock.pde https://www.openprocessing.org/sketch/380215/ */ int boidCount = 100; // make parameters visible on canvas float boidStartingSpeed = .1; float boidMaxSpeed = 2; float damp = 0.75; float cohesionRange = 50; float separationRange = 20; float alignmentRange = 40; float cohesionMag = 0.1; // consider adding magnitudes for separation and alignment ArrayList<Float> speed = new ArrayList<Float>(); ArrayList<Sprite> flock = new ArrayList<Sprite>(); // slider variables boolean cSliding, sSliding, aSliding; int sliderMin = 30; int sliderMax = 130; int sliderRadius = 15; float cSliderX = 80, cSliderY = 80; float aSliderX = 80, aSliderY = 120; float sSliderX = 80, sSliderY = 160; void setup() { size(550, 550); for (int i = 0; i < boidCount; i++) { makeBoid(width/2, height/2); } } void draw() { background(0); for (Sprite b : flock) { updateBoid(b); b.display(); } drawParams(); } void drawParams() { fill(255, 150); rect(20, 20, 120, 160); fill(0); text("Ranges", 60, 40); text("Cohesion: " + cohesionRange, 30, 70); text("Alignment: " + alignmentRange, 30, 110); text("Separation: " + separationRange, 30, 150); // draw a slider for each stroke(0); line(sliderMin, cSliderY, sliderMax, cSliderY); line(sliderMin, aSliderY, sliderMax, aSliderY); line(sliderMin, sSliderY, sliderMax, sSliderY); ellipse(cSliderX, cSliderY, sliderRadius, sliderRadius); ellipse(aSliderX, aSliderY, sliderRadius, sliderRadius); ellipse(sSliderX, sSliderY, sliderRadius, sliderRadius); // check sliding if(mouseX > sliderMin && mouseX < sliderMax) { if(cSliding) cSliderX = mouseX; if(aSliding) aSliderX = mouseX; if(sSliding) sSliderX = mouseX; } getValues(); } // map slider values to params void getValues() { cohesionRange = int(map(cSliderX, sliderMin, sliderMax, 0, 100)); alignmentRange = int(map(aSliderX, sliderMin, sliderMax, 0, 100)); separationRange = int(map(sSliderX, sliderMin, sliderMax, 0, 40)); } // create a new boid at the mouse position void mousePressed() { if(mouseX > 140 && mouseY > 160) makeBoid(mouseX, mouseY); if(dist(mouseX, mouseY, cSliderX, cSliderY) < sliderRadius) cSliding = true; if(dist(mouseX, mouseY, aSliderX, aSliderY) < sliderRadius) aSliding = true; if(dist(mouseX, mouseY, sSliderX, sSliderY) < sliderRadius) sSliding = true; } void mouseReleased() { cSliding = aSliding = sSliding = false; } // create a new boid in the center of the canvas void makeBoid(float x, float y) { float theta = random(360); Sprite s = new Sprite(x, y, 10, 3); s.turnToDir(theta); s.setColor(random(255),random(255),random(255)); // add boid Sprite to the flock flock.add(s); speed.add(boidStartingSpeed); } // update a boid in the flock and it's speed void updateBoid(Sprite b) { int i = flock.indexOf(b); float theta = radians(b.getDir()); PVector v = new PVector(speed.get(i)*cos(theta), speed.get(i)*sin(theta)); PVector sep = separation(b); PVector coh = PVector.mult(cohesion(b), cohesionMag); PVector ali = alignment(b); PVector a = PVector.add(sep, PVector.add(coh, ali)); // add random? v = PVector.mult(PVector.add(a, v), damp); if(v.mag() > boidMaxSpeed){ v.normalize(); v.setMag(boidMaxSpeed); } speed.set(i, v.mag()); float newPosx = b.getX() + v.x; float newPosy = b.getY() + v.y; // wrap around on the screen newPosx = (newPosx + width) % width; newPosy = (newPosy + height) % height; b.setX(newPosx); b.setY(newPosy); b.turnToDir(degrees(v.heading())); } /* Return the separation PVector for a boid in the flock - Find all other boids within separationRange - For each boid within range, create a vector from that boid to this one - Add together vectors with force inversely proportional to distance */ PVector separation(Sprite b) { PVector r = new PVector(); ArrayList<Sprite> neighbors = new ArrayList<Sprite>(); // check for boids within collision range for(Sprite s : flock) { if(b.distTo(s) < separationRange) { neighbors.add(s); } } if(neighbors.size() > 0){ for(Sprite s : neighbors){ // add the contribution of each neighbor as a vector pointed toward this boid PVector towardsMe = new PVector(b.getX() - s.getX(), b.getY() - s.getY()); // force contribution will vary inversely proportional to distance if (towardsMe.mag() > 0) { r = PVector.add(r, PVector.div(towardsMe.normalize(), towardsMe.mag())); } } } return r.normalize(); } /* Return the cohesion PVector for a boid in the flock - Find all boids within cohesionRange - Find the center position of all neighbor boids - Return a vector pointing from this boids position to the center position */ PVector cohesion(Sprite b) { PVector r = new PVector(); ArrayList<Sprite> neighbors = new ArrayList<Sprite>(); for(Sprite s : flock) { if(b.distTo(s) < cohesionRange) { neighbors.add(s); } } // find the center position of this boid’s neighbors if(neighbors.size() > 0) { for (Sprite s : neighbors) { r = PVector.add(r, new PVector(s.getX(), s.getY())); } r = PVector.div(r, neighbors.size()); // find a new vector from this boid’s position to this center position r = PVector.sub(r, new PVector(b.getX(), b.getY())); } return r.normalize(); } /* Return the alignment PVector for a boid in the flock - Find all neighbor boids within alignmentRange - Find the velocity vector of each neighbor - return the normalized average velocity vector */ PVector alignment(Sprite b) { PVector r = new PVector(); ArrayList<Sprite> neighbors = new ArrayList<Sprite>(); int i = flock.indexOf(b); for(Sprite s : flock) { if(b.distTo(s) < alignmentRange) { neighbors.add(s); } } // add together the velocity vectors of all boids in neighbors if(neighbors.size() > 0) { for (Sprite s : neighbors) { float theta = radians(s.getDir()); float vx = speed.get(i)*cos(theta); float vy = speed.get(i)*sin(theta); r = PVector.add(r, new PVector(vx, vy)); } } return r.normalize(); } // consolidated update for using with blocks version /*void bigUpdateBoid(Sprite b) { ArrayList<Float> speeds = speed; float maxSpeed = boidMaxSpeed; int i = flock.indexOf(b); float theta = radians(b.getDir()); PVector v = new PVector(speeds.get(i)*cos(theta), speeds.get(i)*sin(theta)); ArrayList<Sprite> neighbors = new ArrayList<Sprite>(); PVector r = new PVector(); for(Sprite s : flock) { if(b.distTo(s) < separationRange) { neighbors.add(s); } } if(neighbors.size() > 0){ for(Sprite s : neighbors){ PVector towardsMe = new PVector(b.getX() - s.getX(), b.getY() - s.getY()); if (towardsMe.mag() > 0) { r = PVector.add(r, PVector.div(towardsMe.normalize(), towardsMe.mag())); } } } PVector sep = r.normalize(); neighbors.clear(); r = new PVector(); for(Sprite s : flock) { if(b.distTo(s) < cohesionRange) { neighbors.add(s); } } if(neighbors.size() > 0) { for (Sprite s : neighbors) { r = PVector.add(r, new PVector(s.getX(), s.getY())); } r = PVector.div(r, neighbors.size()); r = PVector.sub(r, new PVector(b.getX(), b.getY())); } PVector coh = PVector.mult(r.normalize(), 0.1); neighbors.clear(); r = new PVector(); for(Sprite s : flock) { if(b.distTo(s) < alignmentRange) { neighbors.add(s); } } if(neighbors.size() > 0) { for (Sprite s : neighbors) { float dir = radians(s.getDir()); float vx = speeds.get(i)*cos(dir); float vy = speeds.get(i)*sin(dir); r = PVector.add(r, new PVector(vx, vy)); } } PVector ali = r.normalize(); PVector a = PVector.add(sep, PVector.add(coh, ali)); v = PVector.mult(PVector.add(a, v), damp); if(v.mag() > maxSpeed){ v.normalize(); v.setMag(maxSpeed); } speeds.set(i, v.mag()); float newPosx = b.getX() + v.x; float newPosy = b.getY() + v.y; newPosx = (newPosx + width) % width; newPosy = (newPosy + height) % height; b.setX(newPosx); b.setY(newPosy); b.turnToDir(degrees(v.heading())); }*/