aa develop

開発と成長

ProcessingでパラメータをGUIで操作できるBoids

ProcessingでパラメータをGUIで操作できるBoidsを作成しました。Boidsはパラメータが多数あるので、これでパラメータの影響がわかりやすくなると思います。

f:id:aa_debdeb:20161015170608p:plain

Boids自体は、前に作成した3D版と同じようにProcessingのSampleにあるFlockingを参考にしています。 GUIはControlP5を用いています。

Processingで3DのBoids - aa develop

操作可能なパラメータは以下のとおりです。

  • # BOIDS:Boidの数
  • SEPARATION:Separation(引き離し)の重み、値が大きいほど影響が大きい
  • ALIGNMENT:Alignment(整列)の重み、値が大きいほど影響が大きい
  • COHESION:Cohesion(結合)の重み、値が大きいほど影響が大きい
  • MAX SPEED:Boidの最大速度
  • MAX FORCE:Separation、Alignment、Cohesionで与える最大の力
  • SEPARATION_DISTANCE:Separationの影響を受ける他のBoidsとの最大距離、Toggleボタンで範囲を可視化
  • NEIGHBOR_DISTANCE:Alignment、Cohesionの影響を受ける他のBoidsとの最大距離、Toggleボタンで範囲を可視化
  • USE_TRIANGLE_STYLE:Boidsを三角形で表示する or 点で表示する

Processig3での動作を確認しました。Processing2では、fullScreen()をsize()に変更すれば動くと思います。

以下、コード。

Boids_with_GUI.pde
import controlP5.*;

ControlP5 cp5;
Flock flock;

int NUM_BOIDS;
float SEPARATION_WEIGHT;
float ALIGNMENT_WEIGHT;
float COHESION_WEIGHT;
float MAX_SPEED;
float MAX_FORCE;
float NEIGHBOR_DISTANCE;
float SEPARATION_DISTANCE;
boolean SHOW_NEIGHBOR_DISTANCE;
boolean SHOW_SEPARATION_DISTANCE;
boolean USE_TRIANGLE_STYLE;

void setup() {
  fullScreen();
  //size(800, 600);
  
  cp5 = new ControlP5(this);
  cp5.setColorCaptionLabel(color(128));
  int guiHeight = 20;
  int guiMargin = 7;
  int guiNum = 0;
  cp5.addSlider("NUM_BOIDS")
     .setLabel("# BOIDS")
     .setPosition(guiMargin, (guiMargin + guiHeight) * (guiNum++) + guiMargin)
     .setSize(200, guiHeight)
     .setValue(150)
     .setRange(10, 1000);

  cp5.addSlider("SEPARATION_WEIGHT")
     .setLabel("SEPARATION")
     .setPosition(guiMargin, (guiHeight + guiMargin) * (guiNum++) + guiMargin)
     .setSize(200, guiHeight)
     .setValue(1.5)
     .setRange(0, 3.0);

  cp5.addSlider("ALIGNMENT_WEIGHT")
     .setLabel("ALIGNMENT")
     .setPosition(guiMargin, (guiHeight + guiMargin) * (guiNum++) + guiMargin)
     .setSize(200, guiHeight)
     .setValue(1.0)
     .setRange(0.0, 3.0);

  cp5.addSlider("COHESION_WEIGHT")
     .setLabel("COHESION")
     .setPosition(guiMargin, (guiHeight + guiMargin) * (guiNum++) + guiMargin)
     .setSize(200, guiHeight)
     .setValue(1.0)
     .setRange(0, 3.0);

  cp5.addSlider("MAX_SPEED")
     .setLabel("MAX SPEED")
     .setPosition(guiMargin, (guiHeight + guiMargin) * (guiNum++) + guiMargin)
     .setSize(200, guiHeight)
     .setValue(2.0)
     .setRange(1.0, 5.0);
     
  cp5.addSlider("MAX_FORCE")
   .setLabel("MAX FORCE")
   .setPosition(guiMargin, (guiHeight + guiMargin) * (guiNum++) + guiMargin)
   .setSize(200, guiHeight)
   .setValue(0.03)
   .setRange(0.01, 0.10);

  cp5.addToggle("SHOW_SEPARATION_DISTANCE")
     .setLabel("")
     .setPosition(guiMargin, (guiHeight + guiMargin) * guiNum + guiMargin)
     .setSize(20, guiHeight)
     .setValue(false);

  cp5.addSlider("SEPARATION_DISTANCE")
     .setLabel("SEPARATION DISTANCE")
     .setPosition(20 + guiMargin * 2, (guiHeight + guiMargin) * (guiNum++) + guiMargin)
     .setSize(200 - (20 + guiMargin), guiHeight)
     .setValue(25.0)
     .setRange(10.0, 100.0);

  cp5.addToggle("SHOW_NEIGHBOR_DISTANCE")
     .setLabel("")
     .setPosition(guiMargin, (guiHeight + guiMargin) * guiNum + guiMargin)
     .setSize(20, guiHeight)
     .setValue(false);

  cp5.addSlider("NEIGHBOR_DISTANCE")
     .setLabel("NEIGHBOR DISTANCE")
     .setPosition(20 + guiMargin * 2, (guiHeight + guiMargin) * (guiNum++) + guiMargin)
     .setSize(200 - (20 + guiMargin), guiHeight)
     .setValue(50.0)
     .setRange(10.0, 200.0);

  cp5.addToggle("USE_TRIANGLE_STYLE")
     .setLabel("USE_TRIANGLE_STYLE")
     .setPosition(guiMargin, (guiHeight + guiMargin) * (guiNum++) + guiMargin)
     .setSize(20, guiHeight)
     .setValue(true);
  
  flock = new Flock();
  // Add an initial set of boids into the system
  for (int i = 0; i < NUM_BOIDS; i++) {
    flock.addBoid(new Boid(width/2,height/2));
  }
}

void draw() {
  background(USE_TRIANGLE_STYLE ? 230: 30);
  flock.run();
  while(flock.size() != NUM_BOIDS){
    if(flock.size() < NUM_BOIDS){
      flock.addBoid(new Boid(width/2,height/2));
    } else {
      flock.removeBoid();
    }
  }
}
Boid.pde
// The Boid class

class Boid {

  PVector location;
  PVector velocity;
  PVector acceleration;
  float r;

    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));

    location = new PVector(x, y);
    r = 2.0;
  }

  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
    acceleration.add(force);
  }

  // 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(SEPARATION_WEIGHT);
    ali.mult(ALIGNMENT_WEIGHT);
    coh.mult(COHESION_WEIGHT);
    // Add the force vectors to acceleration
    applyForce(sep);
    applyForce(ali);
    applyForce(coh);
  }

  // Method to update location
  void update() {
    // Update velocity
    velocity.add(acceleration);
    // Limit speed
    velocity.limit(MAX_SPEED);
    location.add(velocity);
    // 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, location);  // A vector pointing from the location to the target
    // Scale to maximum speed
    desired.normalize();
    desired.mult(MAX_SPEED);

    // 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(MAX_SPEED);

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

  void render() {
    // Draw a triangle rotated in the direction of velocity
    float theta = velocity.heading2D() + radians(90);
    // heading2D() above is now heading() but leaving old syntax until Processing.js catches up
    
    if(SHOW_NEIGHBOR_DISTANCE){
      noFill();
      stroke(255, 105, 180, 60);
      ellipse(location.x, location.y, NEIGHBOR_DISTANCE * 2, NEIGHBOR_DISTANCE * 2);
    }
    
    if(SHOW_SEPARATION_DISTANCE){
      noFill();
      stroke(0, 206, 209, 60);
      ellipse(location.x, location.y, SEPARATION_DISTANCE * 2, SEPARATION_DISTANCE * 2);
    }
    if(USE_TRIANGLE_STYLE){
      fill(30);
      noStroke();
      pushMatrix();
      translate(location.x, location.y);
      rotate(theta);
      beginShape(TRIANGLES);
      vertex(0, -r*2);
      vertex(-r, r*2);
      vertex(r, r*2);
      endShape();
      popMatrix();
    } else {
      stroke(230);
      point(location.x, location.y);
    }
  }

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

  // Separation
  // Method checks for nearby boids and steers away
  PVector separate (ArrayList<Boid> boids) {
    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(location, other.location);
      // If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself)
      if ((d > 0) && (d < SEPARATION_DISTANCE)) {
        // Calculate vector pointing away from neighbor
        PVector diff = PVector.sub(location, other.location);
        diff.normalize();
        diff.div(d);        // Weight by distance
        steer.add(diff);
        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(MAX_SPEED);

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

  // Alignment
  // For every nearby boid in the system, calculate the average velocity
  PVector align (ArrayList<Boid> boids) {
    PVector sum = new PVector(0, 0);
    int count = 0;
    for (Boid other : boids) {
      float d = PVector.dist(location, other.location);
      if ((d > 0) && (d < NEIGHBOR_DISTANCE)) {
        sum.add(other.velocity);
        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(MAX_SPEED);

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

  // Cohesion
  // For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location
  PVector cohesion (ArrayList<Boid> boids) {
    PVector sum = new PVector(0, 0);   // Start with empty vector to accumulate all locations
    int count = 0;
    for (Boid other : boids) {
      float d = PVector.dist(location, other.location);
      if ((d > 0) && (d < NEIGHBOR_DISTANCE)) {
        sum.add(other.location); // Add location
        count++;
      }
    }
    if (count > 0) {
      sum.div(count);
      return seek(sum);  // Steer towards the location
    } 
    else {
      return new PVector(0, 0);
    }
  }
}
Flock.pde
// The Flock (a list of Boid objects)

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

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

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

  void addBoid(Boid b) {
    boids.add(b);
  }
  
  void removeBoid(){
    boids.remove(0);
  }
  
  int size(){
    return boids.size();
  }

}