├── README.md ├── Zone.pde ├── Model.pde ├── Boid.pde ├── FlockingBoids.pde └── Observer.pde /README.md: -------------------------------------------------------------------------------- 1 | ## Flocking Boids 2 | 3 | A fancy implementation in Processing of [Craig Reynolds' Boids program](http://www.red3d.com/cwr/boids/), simulating the flocking behavior of birds. Check the [video link](https://vimeo.com/39517129) to see what it looks like. 4 | 5 | Note: This is implemented in Processing 1.5. It won't work properly on Processing 2.0. At some point I will get around to fixing this but for now use the old one to get it working. 6 | 7 | [![Boids on vimeo](https://secure-b.vimeocdn.com/ts/272/606/272606592_640.jpg "Boids")](https://vimeo.com/39517129) 8 | 9 | 10 | ### Dependencies 11 | 12 | To run the code, you need [Processing](http://processing.org) with the following libraries installed: 13 | 14 | * [GLGraphics](http://glgraphics.sourceforge.net/) 15 | * [Toxiclibs](http://toxiclibs.org/) 16 | * [Proscene](http://code.google.com/p/proscene/) 17 | 18 | 19 | ### Usage 20 | 21 | Press the spacebar twice to toggle between first person and third person viewing mode. While in first person mode only (the default), you can fly the camera around the scene by click-dragging the mouse, and zoom with two finger-scroll (mac) or the equivalent. While in third person mode, pressing different numbered keys will trigger third-person presets moving the camera around the scene in various ways. 22 | -------------------------------------------------------------------------------- /Zone.pde: -------------------------------------------------------------------------------- 1 | // class for optimizing boids computation 2 | // complete space is divided into nx x ny x nz cubes of length of boids radius 3 | // every frame, boids are assigned to the cube they are located in, and instead of 4 | // comparing all boids to each other (O(n^2)), boids in each cube are compared 5 | // only to boids in neighbors (and own cube) 6 | 7 | class Zone 8 | { 9 | ArrayList[][][] zones; 10 | ArrayList[][][] neighbors; 11 | int nx, ny, nz, sizex, sizey, sizez, rad; 12 | 13 | Zone(int sizex, int sizey, int sizez, int rad) 14 | { 15 | this.sizex = sizex; 16 | this.sizey = sizey; 17 | this.sizez = sizez; 18 | this.rad = rad; 19 | 20 | // number of zones 21 | nx = floor(sizex / rad); 22 | ny = floor(sizey / rad); 23 | nz = floor(sizez / rad); 24 | 25 | // zone array and neighbors 26 | zones = new ArrayList[nx][ny][nz]; 27 | neighbors = new ArrayList[nx][ny][nz]; 28 | 29 | // initialize 30 | for (int x=0; x(); 34 | } 35 | } 36 | } 37 | 38 | for (int x=0; x(); 42 | } 43 | } 44 | } 45 | } 46 | 47 | ArrayList get(int i, int j, int k) { 48 | return zones[i][j][k]; 49 | } 50 | 51 | ArrayList getNeighbors(int i, int j, int k) { 52 | return neighbors[i][j][k]; 53 | } 54 | 55 | void update() 56 | { 57 | // clear all zones first 58 | for (int x=0; x 0 && d <= neighborhoodRadius) { 63 | velSum.add(b.vel); 64 | posSum.add(b.pos); 65 | repulse = PVector.sub(pos, b.pos); 66 | repulse.normalize(); 67 | repulse.div(d); 68 | posSum2.add(repulse); 69 | count++; 70 | } 71 | } 72 | if (count > 0) { 73 | velSum.div((float) count); 74 | velSum.limit(maxSteerForce); 75 | posSum.div((float) count); 76 | } 77 | ali = velSum; 78 | steer = PVector.sub(posSum, pos); 79 | steer.limit(maxSteerForce); 80 | coh = steer; 81 | sep = posSum2; 82 | acc.add(PVector.mult(ali, 1)); 83 | acc.add(PVector.mult(coh, 3)); 84 | acc.add(PVector.mult(sep, 1)); 85 | } 86 | 87 | void move() { 88 | vel.add(acc); // add acceleration to velocity 89 | vel.limit(maxSpeed); // make sure the velocity vector magnitude does not exceed maxSpeed 90 | pos.add(vel); // add velocity to position 91 | acc.mult(0); // reset acceleration 92 | } 93 | 94 | void checkBounds() { 95 | if (pos.x > boxWidth) pos.x = 0; 96 | if (pos.x < 0) pos.x = boxWidth; 97 | if (pos.y > boxHeight) pos.y = 0; 98 | if (pos.y < 0) pos.y = boxHeight; 99 | if (pos.z > boxDepth) pos.z = 0; 100 | if (pos.z < 0) pos.z = boxDepth; 101 | } 102 | 103 | // steering. If arrival==true, the boid slows to meet the target. Credit to Craig Reynolds 104 | PVector steer(PVector target, boolean arrival) { 105 | PVector steer = new PVector(); // creates vector for steering 106 | if (!arrival) { 107 | steer.set(PVector.sub(target, pos)); // steering vector points towards target (switch target and pos for avoiding) 108 | steer.limit(maxSteerForce); // limits the steering force to maxSteerForce 109 | } 110 | else { 111 | PVector targetOffset = PVector.sub(target, pos); 112 | float distance = targetOffset.mag(); 113 | float rampedSpeed = maxSpeed * (distance / 100); 114 | float clippedSpeed = min(rampedSpeed, maxSpeed); 115 | PVector desiredVelocity = PVector.mult(targetOffset, (clippedSpeed / distance)); 116 | steer.set(PVector.sub(desiredVelocity, vel)); 117 | } 118 | return steer; 119 | } 120 | 121 | // avoid. If weight == true avoidance vector is larger the closer the boid is to the target 122 | PVector avoid(PVector target, boolean weight) { 123 | PVector steer = new PVector(); // creates vector for steering 124 | steer.set(PVector.sub(pos, target)); // steering vector points away from 125 | // target 126 | if (weight) 127 | steer.mult(1 / sq(PVector.dist(pos, target))); 128 | // steer.limit(maxSteerForce); //limits the steering force to 129 | // maxSteerForce 130 | return steer; 131 | } 132 | 133 | PVector seperation(ArrayList boids) { 134 | PVector posSum = new PVector(0, 0, 0); 135 | PVector repulse; 136 | for (int i = 0; i < boids.size(); i++) { 137 | Boid b = (Boid) boids.get(i); 138 | float d = PVector.dist(pos, b.pos); 139 | if (d > 0 && d <= neighborhoodRadius) { 140 | repulse = PVector.sub(pos, b.pos); 141 | repulse.normalize(); 142 | repulse.div(d); 143 | posSum.add(repulse); 144 | } 145 | } 146 | return posSum; 147 | } 148 | 149 | PVector alignment(ArrayList boids) { 150 | PVector velSum = new PVector(0, 0, 0); 151 | int count = 0; 152 | for (int i = 0; i < boids.size(); i++) { 153 | Boid b = (Boid) boids.get(i); 154 | float d = PVector.dist(pos, b.pos); 155 | if (d > 0 && d <= neighborhoodRadius) { 156 | velSum.add(b.vel); 157 | count++; 158 | } 159 | } 160 | if (count > 0) { 161 | velSum.div((float) count); 162 | velSum.limit(maxSteerForce); 163 | } 164 | return velSum; 165 | } 166 | 167 | PVector cohesion(ArrayList boids) { 168 | PVector posSum = new PVector(0, 0, 0); 169 | PVector steer = new PVector(0, 0, 0); 170 | int count = 0; 171 | for (int i = 0; i < boids.size(); i++) { 172 | Boid b = (Boid) boids.get(i); 173 | float d = dist(pos.x, pos.y, b.pos.x, b.pos.y); 174 | if (d > 0 && d <= neighborhoodRadius) { 175 | posSum.add(b.pos); 176 | count++; 177 | } 178 | } 179 | if (count > 0) { 180 | posSum.div((float) count); 181 | } 182 | steer = PVector.sub(posSum, pos); 183 | steer.limit(maxSteerForce); 184 | return steer; 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /FlockingBoids.pde: -------------------------------------------------------------------------------- 1 | // Flocking boids -- Gene Kogan, March 2012 2 | // 3 | // A simulation of flocking based on Craig Reynolds' Boids program. Press 4 | // spacebar twice to toggle between first person and third person mode 5 | // (following a single boid). In first person mode (the default), you can 6 | // fly the camera around the scene by click dragging the mouse, and zoom 7 | // using two-finger scroll. While in third person, pressing different number 8 | // keys will trigger presets which move the camera around the scene in various ways. 9 | // 10 | // If it's running slowly, try reduing the boid count (variable numBoids). 11 | // 12 | // DEPENDENCIES 13 | // To run this code, you must have GLGraphics, Toxiclibs, and Proscene libraries installed: 14 | // GLGraphics : http://glgraphics.sourceforge.net/ 15 | // Toxiclibs : http://toxiclibs.org/ 16 | // Proscene : http://code.google.com/p/proscene/ 17 | // 18 | 19 | import processing.opengl.*; 20 | import codeanticode.glgraphics.*; 21 | import remixlab.proscene.*; 22 | import javax.media.opengl.*; 23 | import toxi.geom.*; 24 | 25 | // initial parameters 26 | int boxWidth = 2400; 27 | int boxHeight = 2400; 28 | int boxDepth = 2400; 29 | int numBoids = 2000; // <-- boid count 30 | boolean avoidWalls = true; 31 | 32 | GLGraphics renderer; 33 | Observer observer; 34 | ArrayList flock; 35 | BoidModel boidModel; 36 | Vec3D[] bverts; 37 | GLModel model; 38 | Zone zones; 39 | float[] verts, rateWing, rateTail; 40 | PVector boidCentroid; 41 | 42 | void setup() 43 | { 44 | size(screenWidth, screenHeight, GLConstants.GLGRAPHICS); 45 | 46 | // create boids 47 | flock = new ArrayList(); 48 | for (int i = 0; i < numBoids; i++) 49 | flock.add(new Boid(new PVector(random(0.33*boxWidth, 0.66*boxWidth), 50 | random(0.33*boxHeight, 0.66*boxHeight), 51 | random(0.33*boxDepth, 0.66*boxDepth)))); 52 | 53 | // set up zones 54 | zones = new Zone(boxWidth,boxHeight,boxDepth,100); 55 | 56 | // boid 3d model 57 | boidModel = new BoidModel(); 58 | verts = new float[ flock.size() * boidModel.indexTri.length * 12 ]; 59 | 60 | // set up GLModel for boids 61 | model = new GLModel(this, 66*flock.size(), TRIANGLES, GLModel.DYNAMIC); 62 | initializeColors(); 63 | 64 | // rate of wing flapping and tail flapping 65 | rateWing = new float[flock.size()]; 66 | rateTail = new float[flock.size()]; 67 | for (int j=0; j neighbors = zones.getNeighbors(i,j,k); 101 | for (Boid b : zones.get(i,j,k)) { 102 | boidCentroid.add(b.pos.x, b.pos.y, b.pos.z); 103 | b.run(neighbors); 104 | } 105 | } 106 | } 107 | } 108 | boidCentroid.mult(1.0 / numBoids); 109 | } 110 | 111 | void updateGLModel() 112 | { 113 | int[][] idx = boidModel.getTriangleIndexes(); // merge index tri and bverts 114 | for (int j = 0; j < flock.size(); j++) 115 | { 116 | // get each boid, and rotate it in direction of its velocity 117 | Boid b = (Boid) flock.get(j); 118 | float angz = atan(b.vel.y / b.vel.x); 119 | float angx = atan(b.vel.y / b.vel.z); 120 | float angy = atan(-b.vel.z / b.vel.x); 121 | if (b.vel.x <0) angy+=PI; 122 | 123 | // flap wings and tails 124 | boidModel.flapWings(rateWing[j] * frameCount); 125 | boidModel.flapTail(rateTail[j] * frameCount); 126 | 127 | // update GLModel's vertices 128 | bverts = boidModel.getVertices(b.pos, angx, angy, angz); 129 | for (int i = 0; i < idx.length; i++) 130 | { 131 | verts[ 264*j + 12*i ] = bverts[idx[i][0]].x; 132 | verts[ 264*j + 12*i + 1 ] = bverts[idx[i][0]].y; 133 | verts[ 264*j + 12*i + 2 ] = bverts[idx[i][0]].z; 134 | verts[ 264*j + 12*i + 3 ] = 1.0; 135 | verts[ 264*j + 12*i + 4 ] = bverts[idx[i][1]].x; 136 | verts[ 264*j + 12*i + 5 ] = bverts[idx[i][1]].y; 137 | verts[ 264*j + 12*i + 6 ] = bverts[idx[i][1]].z; 138 | verts[ 264*j + 12*i + 7 ] = 1.0; 139 | verts[ 264*j + 12*i + 8 ] = bverts[idx[i][2]].x; 140 | verts[ 264*j + 12*i + 9 ] = bverts[idx[i][2]].y; 141 | verts[ 264*j + 12*i + 10 ] = bverts[idx[i][2]].z; 142 | verts[ 264*j + 12*i + 11 ] = 1.0; 143 | } 144 | } 145 | model.updateVertices(verts); 146 | } 147 | 148 | void initializeColors() { 149 | model.initColors(); 150 | model.beginUpdateColors(); 151 | for (int i=0; i= 1.0) { 50 | this.targetIdx1 = targetIdx2; 51 | t0 = 1.0; 52 | targetTransitioning = false; 53 | } 54 | } else { 55 | if (mode==1) { 56 | Boid b = (Boid) flock.get(targetIdx1); 57 | avatar.setPosition(boidCentroid.x, boidCentroid.y, boidCentroid.z); 58 | } else if (mode==2) { 59 | Boid b = (Boid) flock.get(targetIdx1); 60 | avatar.setPosition(b.pos.x, b.pos.y, b.pos.z); 61 | } 62 | } 63 | // update azimuth, inclination, and distance 64 | avatar.setAzimuth(boidAzimuth.next()); 65 | avatar.setInclination(boidInclination.next()); 66 | avatar.setTrackingDistance(boidDistance.next()); 67 | } 68 | 69 | void setFollowingBoid() { 70 | mode = 2; 71 | targetIdx2 = (int) random(flock.size()); 72 | targetTransitioning = true; 73 | t0 = 0.0; 74 | } 75 | 76 | void setFollowingCentroid() { 77 | mode = 1; 78 | targetIdx2 = 0; 79 | targetTransitioning = true; 80 | t0 = 0.0; 81 | } 82 | 83 | void zoomInToCentroid() { 84 | boidDistance.set(avatar.trackingDistance(), 1100, 540); 85 | } 86 | 87 | void arcAroundCentroid1() { 88 | boidAzimuth.set(avatar.azimuth(), avatar.azimuth() + 3*HALF_PI, avatar.azimuth() + PI/4, 700); 89 | boidInclination.set(avatar.inclination(), avatar.inclination() + PI/3, avatar.inclination() + PI/6, 700); 90 | boidDistance.set(avatar.trackingDistance(), 500, 1100, 700); 91 | } 92 | 93 | void arcAroundCentroid2() { 94 | boidAzimuth.set(avatar.azimuth(), avatar.azimuth() - 5*PI/4, avatar.azimuth() - HALF_PI, 850); 95 | boidInclination.set(avatar.inclination(), avatar.inclination() + PI/4, avatar.inclination() + PI/8, 800); 96 | boidDistance.set(avatar.trackingDistance(), 1600, 500, 900); 97 | } 98 | 99 | void arcAroundBoid1() { 100 | boidAzimuth.set(avatar.azimuth(), avatar.azimuth() + 3*HALF_PI, avatar.azimuth() + PI/3, 750); 101 | boidInclination.set(avatar.inclination(), avatar.inclination() + PI/5, avatar.inclination() + PI/8, 650); 102 | boidDistance.set(avatar.trackingDistance(), 300, 75, 900); 103 | } 104 | 105 | void arcAroundBoid2() { 106 | boidAzimuth.set(avatar.azimuth(), avatar.azimuth() - HALF_PI, avatar.azimuth() - PI/4, 300); 107 | boidInclination.set(avatar.inclination(), avatar.inclination() - PI/3, avatar.inclination() - PI/4, 250); 108 | boidDistance.set(avatar.trackingDistance(), 1000, 550, 500); 109 | } 110 | 111 | void zoomOutFromBoid() { 112 | boidAzimuth.set(avatar.azimuth(), avatar.azimuth() + PI/4, 500); 113 | boidDistance.set(avatar.trackingDistance(), 1400, 600); 114 | } 115 | 116 | void zoomOutFromCentroid() { 117 | boidAzimuth.set(avatar.azimuth(), avatar.azimuth() - HALF_PI, 900); 118 | boidDistance.set(avatar.trackingDistance(), 4000, 900); 119 | } 120 | 121 | PVector tweenFollow(int idxfollow1, int idxfollow2, float t) 122 | { 123 | float t2 = 0.5 * (1 + cos(PI*t)); 124 | PVector tween1 = new PVector(0, 0, 0); 125 | if (idxfollow1==0) 126 | tween1 = boidCentroid; 127 | else { 128 | Boid b = (Boid) flock.get(idxfollow1); 129 | tween1 = new PVector(b.pos.x, b.pos.y, b.pos.z); 130 | } 131 | PVector tween2 = new PVector(0, 0, 0); 132 | if (idxfollow2==0) 133 | tween2 = boidCentroid; 134 | else { 135 | Boid b = (Boid) flock.get(idxfollow2); 136 | tween2 = new PVector(b.pos.x, b.pos.y, b.pos.z); 137 | } 138 | return new PVector(lerp(tween2.x, tween1.x, t2), lerp(tween2.y, tween1.y, t2), lerp(tween2.z, tween1.z, t2)); 139 | } 140 | 141 | private class Trajectory 142 | { 143 | float value; 144 | float minValue, maxValue, midValue; 145 | int numFrames, n; 146 | boolean hasMidValue, active; 147 | 148 | Trajectory(float value) { 149 | this.value = value; 150 | numFrames = 0; 151 | } 152 | 153 | void set(float minValue, float maxValue, int numFrames) 154 | { 155 | this.minValue = minValue; 156 | this.maxValue = maxValue; 157 | this.numFrames = numFrames; 158 | n = 0; 159 | hasMidValue = false; 160 | value = minValue; 161 | } 162 | 163 | void set(float minValue, float midValue, float maxValue, int numFrames) 164 | { 165 | this.minValue = minValue; 166 | this.midValue = midValue; 167 | this.maxValue = maxValue; 168 | this.numFrames = numFrames; 169 | n = 0; 170 | hasMidValue = true; 171 | value = minValue; 172 | } 173 | 174 | float next() 175 | { 176 | if (n < numFrames) { 177 | float t0 = (float) n / (numFrames-1); 178 | if (!hasMidValue) { 179 | float t = 0.5 * (1.0 - cos(PI * t0)); 180 | value = lerp(minValue, maxValue, t); 181 | } else { 182 | float t = 0.5 * (1.0 - cos(TWO_PI * t0)); 183 | if (t0 < 0.5) { 184 | value = lerp(minValue, midValue, t); 185 | } else { 186 | value = lerp(maxValue, midValue, t); 187 | } 188 | } 189 | n++; 190 | } 191 | return value; 192 | } 193 | } 194 | } 195 | --------------------------------------------------------------------------------