Reynolds boids (using Look Compute Move)

By Martin Rosalie

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 position of a boid in round i is determined by the position and speed of its neighbors in round i-1.

You can download an executable JAR that contains the source code and a copy of JBotSim.
(Run by typing java -jar boids.jar.)

Timing

In JBotSim, we redefine the following 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)
            this.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 // Move
public void onPostClock(){
    Point target = new Point();
    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) {
        if(!aBoid.equals(this)) {
            center.add(aBoid.getLocation().getX(),aBoid.getLocation().getY());
        }
    }
    if (theNeighbors.size()>0){
        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(!aBoid.equals(this)) {
            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) {
        if(!aBoid.equals(this)) {
            global.add(aBoid.getVelocity());
        }
    }
    if (theNeighbors.size()>0){
        global.division(theNeighbors.size());
        global.substract(velocity);
        global.division(8.);
    }
    return global;
}

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