12.5. The Boid Algorithm

Boids7.py defines two classes: Boid, which implements the Boid behaviors, and World, which contains a list of Boids and a “carrot” the Boids are attracted to.

The Boid class defines the following methods:

Here’s the implementation of center:

def center(self, boids, radius=1, angle=1):
        neighbors = self.get_neighbors(boids, radius, angle)
        vecs = [boid.pos for boid in neighbors]
        return self.vector_toward_center(vecs)

The parameters radius and angle are the radius and angle of the field of view, which determines which other Boids are taken into consideration. radius is in arbitrary units of length; angle is in radians.

center USES get_neighbors to get a list of Boid objects that are in the field of view. vecs is a list of Vector objects that represent their positions.

Finally, vector_toward_center computes a Vector that points from self to the centroid of neighbors.

Here’s how get_neighbors works:

def get_neighbors(self, boids, radius, angle):
        neighbors = []
        for boid in boids:
            if boid is self:
                continue

            # if not in range, skip it
            offset = boid.pos - self.pos
            if offset.mag > radius:
                continue

            # if not within viewing angle, skip it
            if self.vel.diff_angle(offset) > angle:
                continue

            # otherwise add it to the list
            neighbors.append(boid)

        return neighbors

For each other Boid, get_neighbors uses vector subtraction to compute the vector from self to boid. The magnitude of this vector is the distance between them; if this magnitude exceeds radius, we ignore boid.

diff_angle computes the angle between the velocity of self, which points in the direction the Boid is heading, and the position of boid. If this angle exceeds angle, we ignore boid.

Otherwise boid is in view, so we add it to neighbors.

Now here’s the implementation of vector_toward_center, which computes a vector from self to the centroid of its neighbors.

def vector_toward_center(self, vecs):
        if vecs:
            center = np.mean(vecs)
            toward = vector(center - self.pos)
            return limit_vector(toward)
        else:
            return null_vector

VPython vectors work with NumPy, so np.mean computes the mean of vecs, which is a sequence of vectors. limit_vector limits the magnitude of the result to 1; null_vector has magnitude 0.

We use the same helper methods to implement avoid:

def avoid(self, boids, carrot, radius=0.3, angle=np.pi):
        objects = boids + [carrot]
        neighbors = self.get_neighbors(objects, radius, angle)
        vecs = [boid.pos for boid in neighbors]
        return -self.vector_toward_center(vecs)

avoid is similar to center, but it takes into account the carrot as well as the other Boids. Also, the parameters are different: radius is smaller, so Boids only avoid objects that are too close, and angle is wider, so Boids avoid objects from all directions. Finally, the result from vector_toward_center is negated, so it points away from the centroid of any objects that are too close.

Here’s the implementation of align:

def align(self, boids, radius=0.5, angle=1):
        neighbors = self.get_neighbors(boids, radius, angle)
        vecs = [boid.vel for boid in neighbors]
        return self.vector_toward_center(vecs)

align is also similar to center; the big difference is that it computes the average of the neighbors’ velocities, rather than their positions. If the neighbors point in a particular direction, the Boid tends to steer toward that direction.

Finally, love computes a vector that points in the direction of the carrot.

def love(self, carrot):
        toward = carrot.pos - self.pos
        return limit_vector(toward)

The results from center, avoid, align, and love are what Reynolds calls “acceleration requests”, where each request is intended to achieve a different goal.

You have attempted of activities on this page