Multiple type of boids flocking simulation

Hi, I am trying to create a flocking system based on Craig Reynolds Boids, which contain multiple type of boids. And each type of boids will interact with the other type of boids.

Like this.( It is written by Husselmann & Hawick)

And I changed Daniel’s example file. Now two different type of boids are created.
But how to make them to interact with the other.

``````

Flock flock;

void setup() {
size(1280, 768);
flock = new Flock();
// Add an initial set of boids into the system
for (int i = 0; i < 200; i++) {
}
for ( int i = 0; i< 150 ; i++){
}

}

void draw() {
background(255);
flock.run();
}

// Add a new boid into the System
void mousePressed() {
}

// The Flock (a list of Boid objects)

class Flock {
ArrayList<Boid> boids; // An ArrayList for all the boids
ArrayList<BoidB>boidsB;

Flock() {
boids = new ArrayList<Boid>(); // Initialize the ArrayList
boidsB = new ArrayList<BoidB>();
}

void run() {
for (Boid x : boids) {
x.run(boids);  // Passing the entire list of boids to each boid individually
}
for (BoidB y : boidsB){
y. run(boidsB);
}

}

}
}

}

// The Boid class

class Boid {

PVector position; //PVector向量
PVector velocity;
PVector acceleration;
float r;
float maxforce;    // Maximum steering force
float maxspeed;    // Maximum speed

Boid(float x, float y) {
acceleration = new PVector(0, 0);

// This is a new PVector method not yet implemented in JS
// velocity = PVector.random2D();

// Leaving the code temporarily this way so that this example runs in JS
float angle = random(TWO_PI);
velocity = new PVector(cos(angle), sin(angle));

position = new PVector(x, y);
r = 2.0;
maxspeed = 2;
maxforce = 0.03;
}

void run(ArrayList<Boid> boids) {
flock(boids);
update();
borders();
render();
}

void applyForce(PVector force) {
// We could add mass here if we want A = F / M
}

// We accumulate a new acceleration each time based on three rules
void flock(ArrayList<Boid> boids) {
PVector sep = separate(boids);   // Separation
PVector ali = align(boids);      // Alignment
PVector coh = cohesion(boids);   // Cohesion
// Arbitrarily weight these forces
sep.mult(1.5);
ali.mult(1.0);
coh.mult(1.0);
// Add the force vectors to acceleration
applyForce(sep);
applyForce(ali);
applyForce(coh);
}

// Method to update position
void update() {
// Update velocity
// Limit speed
velocity.limit(maxspeed);
// Reset accelertion to 0 each cycle
acceleration.mult(0);
}

// A method that calculates and applies a steering force towards a target
// STEER = DESIRED MINUS VELOCITY
PVector seek(PVector target) {
PVector desired = PVector.sub(target, position);  // A vector pointing from the position to the target
// Scale to maximum speed
desired.normalize();
desired.mult(maxspeed);

// Above two lines of code below could be condensed with new PVector setMag() method
// Not using this method until Processing.js catches up
// desired.setMag(maxspeed);

// Steering = Desired minus Velocity
PVector steer = PVector.sub(desired, velocity);
steer.limit(maxforce);  // Limit to maximum steering force
return steer;
}

void render() {
// Draw a triangle rotated in the direction of velocity
// heading2D() above is now heading() but leaving old syntax until Processing.js catches up

fill(255, 140,140);
stroke(255, 140,140);
pushMatrix();
translate(position.x, position.y);
rotate(theta);
beginShape(TRIANGLES);
vertex(0, -r*2);
vertex(-r, r*2);
vertex(r, r*2);
endShape();
popMatrix();
}

// Wraparound
void borders() {
if (position.x < -r) position.x = width+r;
if (position.y < -r) position.y = height+r;
if (position.x > width+r) position.x = -r;
if (position.y > height+r) position.y = -r;
}

// Separation
// Method checks for nearby boids and steers away
PVector separate (ArrayList<Boid> boids) {
float desiredseparation = 25.0f;
PVector steer = new PVector(0, 0, 0);
int count = 0;
// For every boid in the system, check if it's too close
for (Boid other : boids) {
float d = PVector.dist(position, other.position);
// If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself)
if ((d > 0) && (d < desiredseparation)) {
// Calculate vector pointing away from neighbor
PVector diff = PVector.sub(position, other.position);
diff.normalize();
diff.div(d);        // Weight by distance
count++;            // Keep track of how many
}
}
// Average -- divide by how many
if (count > 0) {
steer.div((float)count);
}

// As long as the vector is greater than 0
if (steer.mag() > 0) {
// First two lines of code below could be condensed with new PVector setMag() method
// Not using this method until Processing.js catches up
// steer.setMag(maxspeed);

// Implement Reynolds: Steering = Desired - Velocity
steer.normalize();
steer.mult(maxspeed);
steer.sub(velocity);
steer.limit(maxforce);
}
return steer;
}

// Alignment
// For every nearby boid in the system, calculate the average velocity
PVector align (ArrayList<Boid> boids) {   //<>li
float neighbordist = 50;
PVector sum = new PVector(0, 0);
int count = 0;
for (Boid other : boids) {
float d = PVector.dist(position, other.position);
if ((d > 0) && (d < neighbordist)) {
count++;
}
}
if (count > 0) {
sum.div((float)count);
// First two lines of code below could be condensed with new PVector setMag() method
// Not using this method until Processing.js catches up
// sum.setMag(maxspeed);

// Implement Reynolds: Steering = Desired - Velocity
sum.normalize();
sum.mult(maxspeed);
PVector steer = PVector.sub(sum, velocity);
steer.limit(maxforce);
return steer;
}
else {
return new PVector(0, 0);
}
}

// Cohesion
// For the average position (i.e. center) of all nearby boids, calculate steering vector towards that position
PVector cohesion (ArrayList<Boid> boids) {
float neighbordist = 50;
PVector sum = new PVector(0, 0);   // Start with empty vector to accumulate all positions
int count = 0;
for (Boid other : boids) {
float d = PVector.dist(position, other.position);
if ((d > 0) && (d < neighbordist)) {
count++;
}
}
if (count > 0) {
sum.div(count);
return seek(sum);  // Steer towards the position
}
else {
return new PVector(0, 0);
}
}
}

class BoidB {

PVector position; //PVector向量
PVector velocity;
PVector acceleration;
float r;
float maxforce;    // Maximum steering force
float maxspeed;    // Maximum speed

BoidB(float x, float y) {
acceleration = new PVector(0, 0);

// This is a new PVector method not yet implemented in JS
// velocity = PVector.random2D();

// Leaving the code temporarily this way so that this example runs in JS
float angle = random(TWO_PI);
velocity = new PVector(cos(angle), sin(angle));

position = new PVector(x, y);
r = 2.0;
maxspeed = 2;
maxforce = 0.03;
}

void run(ArrayList<BoidB> boidsB) {
flock(boidsB);
update();
borders();
render();
}

void applyForce(PVector force) {
// We could add mass here if we want A = F / M
}

// We accumulate a new acceleration each time based on three rules
void flock(ArrayList<BoidB> boidsB) {
PVector sep = separate(boidsB);   // Separation
PVector ali = align(boidsB);      // Alignment
PVector coh = cohesion(boidsB);   // Cohesion
// Arbitrarily weight these forces
sep.mult(1.5);
ali.mult(1.0);
coh.mult(1.0);
// Add the force vectors to acceleration
applyForce(sep);
applyForce(ali);
applyForce(coh);
}

// Method to update position
void update() {
// Update velocity
// Limit speed
velocity.limit(maxspeed);
// Reset accelertion to 0 each cycle
acceleration.mult(0);
}

// A method that calculates and applies a steering force towards a target
// STEER = DESIRED MINUS VELOCITY
PVector seek(PVector target) {
PVector desired = PVector.sub(target, position);  // A vector pointing from the position to the target
// Scale to maximum speed
desired.normalize();
desired.mult(maxspeed);

// Above two lines of code below could be condensed with new PVector setMag() method
// Not using this method until Processing.js catches up
// desired.setMag(maxspeed);

// Steering = Desired minus Velocity
PVector steer = PVector.sub(desired, velocity);
steer.limit(maxforce);  // Limit to maximum steering force
return steer;
}

void render() {
// Draw a triangle rotated in the direction of velocity
// heading2D() above is now heading() but leaving old syntax until Processing.js catches up

fill(50, 100,150);
stroke(150);
pushMatrix();
translate(position.x, position.y);
rotate(theta);
beginShape(TRIANGLES);
vertex(0, -r*2);
vertex(-r, r*2);
vertex(r, r*2);
endShape();
popMatrix();
}

// Wraparound
void borders() {
if (position.x < -r) position.x = width+r;
if (position.y < -r) position.y = height+r;
if (position.x > width+r) position.x = -r;
if (position.y > height+r) position.y = -r;
}

// Separation
// Method checks for nearby boids and steers away
PVector separate (ArrayList<BoidB> boidsB) {
float desiredseparation = 25.0f;
PVector steer = new PVector(0, 0, 0);
int count = 0;
// For every boid in the system, check if it's too close
for (BoidB other : boidsB) {
float d = PVector.dist(position, other.position);
// If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself)
if ((d > 0) && (d < desiredseparation)) {
// Calculate vector pointing away from neighbor
PVector diff = PVector.sub(position, other.position);
diff.normalize();
diff.div(d);        // Weight by distance
count++;            // Keep track of how many
}
}
// Average -- divide by how many
if (count > 0) {
steer.div((float)count);
}

// As long as the vector is greater than 0
if (steer.mag() > 0) {
// First two lines of code below could be condensed with new PVector setMag() method
// Not using this method until Processing.js catches up
// steer.setMag(maxspeed);

// Implement Reynolds: Steering = Desired - Velocity
steer.normalize();
steer.mult(maxspeed);
steer.sub(velocity);
steer.limit(maxforce);
}
return steer;
}

// Alignment
// For every nearby boid in the system, calculate the average velocity
PVector align (ArrayList<BoidB> boidsB) {
float neighbordist = 50;
PVector sum = new PVector(0, 0);
int count = 0;
for (BoidB other : boidsB) {
float d = PVector.dist(position, other.position);
if ((d > 0) && (d < neighbordist)) {
count++;
}
}

if (count > 0) {
sum.div((float)count);
// First two lines of code below could be condensed with new PVector setMag() method
// Not using this method until Processing.js catches up
// sum.setMag(maxspeed);

// Implement Reynolds: Steering = Desired - Velocity
sum.normalize();
sum.mult(maxspeed);
PVector steer = PVector.sub(sum, velocity);
steer.limit(maxforce);
return steer;
}
else {
return new PVector(0, 0);
}
}

// Cohesion
// For the average position (i.e. center) of all nearby boids, calculate steering vector towards that position
PVector cohesion (ArrayList<BoidB> boidsB) {
float neighbordist = 50;
PVector sum = new PVector(0, 0);   // Start with empty vector to accumulate all positions
int count = 0;
for (BoidB other : boidsB) {
float d = PVector.dist(position, other.position);
if ((d > 0) && (d < neighbordist)) {
count++;
}
}
if (count > 0) {
sum.div(count);
return seek(sum);  // Steer towards the position
}
else {
return new PVector(0, 0);
}
}
}
``````

Any help is appreciated!

Hi,

Welcome to the forum!

The problem you want to solve is a nice example of the concept of inheritance in programming.

Let’s suppose you have an abstract class named `Boid` :

``````abstract class Boid {
abstract PVector separate(ArrayList<Boid> boids);
abstract PVector align(ArrayList<Boid> boids);
abstract PVector seek(PVector target);
//...
}
``````

Here we use an abstract class because we want to create different types of boids that are going to define the abstract methods specifically for their behavior :

``````class BoidA extends Boid {
PVector separate(ArrayList<Boid> boids) {
// separate behavior of BoidA
}
// same for other abstract methods...
}

class BoidB extends Boid {
abstract PVector align(ArrayList<Boid> boids) {
// align behavior for BoidB
}
// same for other abstract methods...
}
``````

Now you can extend the `Boid` class as much as you want and the nice thing about this is that all the boid classes can be manipulated as `Boid` :

``````ArrayList<Boid> boids = new ArrayList<Boid>();

// Loop through the boids
for (Boid b : boids) {
b.separate(); // call the apropriate method through polymorphism
b.align();
// etc...
}
``````

The concept behind this is polymorphism, when calling the `separate()` method on the `Boid` class, it’s going to call the appropriate `BoidA` or `BoidB` method that you defined.

In addition, different boids are going to interact with each other because they will manipulate objects of the parent class `Boid` rather than boids of their own type!

Hope it’s clear!

2 Likes

Thank you Joseph!
I try to solve the problem under your recommendation.

1 Like