/* Our Boid objects build on our work with Particle objects. We need to add a few methods to create the larger effects we see in a Boid system. */ public class Boid extends Particle { // Keep track of the direction we wander float wanderTheta; // Maximum speed to move float maxSpeed; // Maximum acceleration force float maxForce; public Boid(Vector3D loc_, float maxSpeed_, float maxForce_) { // call the Particle constructor super(loc_); // the size of our boid r = 4.0f; // The start direction to wander in wanderTheta = 0.0f; // Store our maximum values maxSpeed = maxSpeed_; maxForce = maxForce_; } /* Our familiar simulate method, with one change; the borders() call allows our Boid to wrap around the edges of the screen. */ public void simulate() { update(); borders(); render(); } /* The update method follows the basic model of Vector motion. We need to be careful to reset the acceleration each time. */ public void update() { vel = vel.add(accel); vel.limit(maxSpeed); loc = loc.add(vel); accel.setXYZ(0, 0, 0); } /* Using the seek method the boid attempts to move towards a specific location. */ public void seek(Vector3D target) { accel = accel.add(steer(target, false)); } /* Arrive is similar to seek, except the boid will slow down and stop when it reaches its target destination. */ public void arrive(Vector3D target) { accel = accel.add(steer(target, true)); } /* Sometimes we want our boid to just wander around the screen. Instead of randomly choosing a new direction every time, it should attempt a "directed" random wandering. Each time we call wander() we change our wanderTheta a small bit, creating random movement somewhat more like the random noise from Perlin noise. */ public void wander() { /* The wander method projects a virtual circle in front of the boid, using the wanderTheta to determine the point towards which the boid should move. */ // Radius of the circle float wanderRadius = 40.0f; // Distance the cirlce is offset from the boid float wanderDistance = 80.0f; // Range in which our wanderTheta can change each time float wanderChange = 0.5f; // Adjust our wanderTheta wanderTheta += random(-wanderChange, wanderChange); // start with a copy of the boid velocity Vector3D circleLoc = vel.copy(); // normalize and scale to the wanderDistance offset circleLoc.normalize(); circleLoc = circleLoc.multiply(wanderDistance); // position it relative to the boid location circleLoc.add(loc); // Figure out the wander circle heading float actualTheta = wanderTheta + vel.heading2D(); // Determine the vector showing the offset of the point on the circle Vector3D circleOffset = new Vector3D(wanderRadius * cos(actualTheta), wanderRadius * sin(actualTheta), 0); // figure out the target position of the wander point Vector3D target = Vector3D.add(circleLoc, circleOffset); // get our acceleration towards our wander point accel = accel.add(steer(target, false)); } /* Calculate a steering vector towards a target position. If the second argument is true, the acceleration will slowdown as the boid approaches the target. If the second argument is false, the boid will not slow down. */ public Vector3D steer(Vector3D target, boolean slowdown) { // The steering vector Vector3D steer; // A vector pointing from the boid to the target Vector3D desiredLoc = Vector3D.subtract(target, loc); // get the distance to the target float d = desiredLoc.magnitude(); // If the distance to the target is greater than zero, calculate a steering // force, otherwise the steering force is 0 if (d > 0) { // normalize the desired location target desiredLoc.normalize(); // figure out if we need to slow down if ( (slowdown) && (d < 100.0f)) { // apply a max speed inversely proportional to how close we are to the target desiredLoc = desiredLoc.multiply(maxSpeed * (d / 100.0f)); } else { // apply our maximum speed desiredLoc = desiredLoc.multiply(maxSpeed); } // get a steering force towards our target steer = Vector3D.subtract(desiredLoc, vel); // limit our acceleration force steer.limit(maxForce); } else { // no acceleration steer = new Vector3D(0, 0, 0); } return steer; } /* Our render method uses popMatrix and pushMatrix to keep track of our screen location so we can adjust the screen to make drawing our Boid much easier. */ public void render() { // figure out what direction our boid points in float theta = vel.heading2D() + radians(90); // set the fill color and drawing style fill(32, 64, 128); stroke(255); // save our screen position pushMatrix(); // translate to the location of our boid translate(loc.getX(), loc.getY()); // rotate so we point the screen in the direction our boid // points in rotate(theta); // draw a triangle around our boid beginShape(TRIANGLES); vertex(0, -r * 2); vertex(-r, r * 2); vertex(r, r * 2); endShape(); // retrieve our screen position popMatrix(); } /* Wrap our Boid's position around the screen. */ public void borders() { if (loc.getX() < -r) loc.setX(width + r); if (loc.getY() < -r) loc.setY(height + r); if (loc.getX() > width + r) loc.setX(-r); if (loc.getY() > height + r) loc.setY(-r); } }