Reynolds boids (using Look Compute Move)

By Martin Rosalie.

You can download an executable JAR that contains the source code and a copy of JBotSim.

This example explains how to implement Craig Reynold's Boids [1] in JBotSim. The purpose is to simulate bird flocks. There is no global behavior implemented because the flock behavior emerge from three rules. We use the boids pseudocode written by Conrad Parker [2]. The next position of a boid is determined by the position and speed of its neighbors.

Timing: In JBotSim, we use the @override methods: onPreClock, onClock and onPostClock. We have the guarantee that onPreClock will first executes on all the boids before onClock is executed, and same between onClock and onPostClock. Hence, all the boids first find their neighbors, then they compute the next location, then apply the resulting movement.

@Override // Look around
public void onPreClock(){
    theNeighbors.clear();
    List<Node> sensedNode = getSensedNodes();
    for (Node aNode : sensedNode)
        if (aNode instanceof Boid)
            theNeighbors.add((Boid) aNode);
}

@Override // Compute movement
public void onClock(){
    nextVelocity = velocity;
    nextVelocity.add(groupFlock());
    nextVelocity.add(collisionAvoidance());
    nextVelocity.add(matchVelocity());
    nextVelocity.add(new Vector2D(0.02,0.0)); // Wind
    nextVelocityMax(); // Animal limitation
}

@Override // Movement
public void onPostClock(){
    Point2D target = new Point2D.Double();
    velocity = nextVelocity;
    target.setLocation(getLocation().getX()+velocity.getX(),
                       getLocation().getY()+velocity.getY());
    setDirection(target);
    move(velocity.distance());
    wrapLocation();
}    

Each boid has three rules:

We also add a method to limit the speed of the boids.

public Vector2D groupFlock(){
    Vector2D center = new Vector2D();
    for (Boid aBoid : theNeighbors)
        center.add(aBoid.getLocation().getX(),aBoid.getLocation().getY());
    if (!theNeighbors.isEmpty()){
        center.division(theNeighbors.size());
        center.substract(getLocation().getX(),getLocation().getY());
        center.division(1000.);
    }
    return center;
}

public Vector2D collisionAvoidance(){
    Vector2D avoid = new Vector2D();
    for (Boid aBoid : theNeighbors) {
        if(distance(aBoid)<60){
            Vector2D tempDistance = new Vector2D(
                aBoid.getLocation().getX()-getLocation().getX(),
                aBoid.getLocation().getY()-getLocation().getY());
            avoid.substract(tempDistance);
        }
    }
    avoid.division(20.);
    return avoid;
}

public Vector2D matchVelocity(){
    Vector2D global = new Vector2D();
    for (Boid aBoid : theNeighbors) 
        global.add(aBoid.getVelocity());
    if (!theNeighbors.isEmpty()){
        global.division(theNeighbors.size());
        global.substract(velocity);
        global.division(8.);
    }
    return global;
}    

[1] Reynold's page, http://www.red3d.com/cwr/boids/
[2] Parker's page, http://www.kfish.org/boids/pseudocode.html