Previous page: Daily Crap 2009-02-20
Next page: Daily Crap 2009-03-13


Flocking Critters in Processing (lazy day sketch)

Just a Processing sketch for animating the motions of a bunch of critters moving around a bounded 2-D field with a simple flocking algorithm (average movement directions of every nearest-neighbor pair). Really just a distraction…

int width = 800;
int height = 600;
float bouncebuffer = 0.1; // how far past the screen world boundaries are
                          // try making this negative!

int numcritters = 150;
int traillength = 100;
float vel = 80;
int colorlevels = 3;
int colors[] = new int[] {color(255, 0, 0), color(255, 255, 255), color(0, 0, 255)};

Critter[] critters = new Critter[numcritters];

class Critter {
  public float x, y, v, angle;
  private float[] xtrail, ytrail;
  public Critter(float x, float y, float v, float angle) {
    this.x = x;
    this.y = y;
    this.v = v;
    this.angle = angle;
    xtrail = new float[traillength];
    ytrail = new float[traillength];
    for(int i = 0; i < xtrail.length; i++) {
      xtrail[i] = x;
      ytrail[i] = y;
    }
  }

  public void update(float seconds) {
    x += v * seconds * cos(angle);
    y += v * seconds * sin(angle);

    Critter neighbor = nearest(this);

    // collisions aren't handled right. but it's RIGHT ENOUGH, JERK
    if(y < -height * bouncebuffer || y > height + height * bouncebuffer)
      angle = -angle;
    if(x < -width * bouncebuffer || x > width + width * bouncebuffer)
      angle = PI + angle;
    else {
      angle += anglediff(angle, neighbor.angle) * 0.5;
      angle += random(-0.3, 0.3);
    }

    shiftTrail();
    xtrail[xtrail.length - 1] = x;
    ytrail[ytrail.length - 1] = y;
  }

  public void draw() {
    stroke(128);
    for(int i = 0; i < xtrail.length - 1; i++) {
      // lots of options here
      //stroke(255 - abs((float) i / (float) xtrail.length - 0.5) * 2 * 255);
      //stroke(255 - (float) i / (float) xtrail.length * 255);
      //stroke(col(xtrail[i], ytrail[i]));
      //stroke(colscale(col(xtrail[i], ytrail[i]), 1 - abs((float) i / (float) xtrail.length - 0.5) * 2));
      stroke(colscale(col(x, y), 1 - abs((float) i / (float) xtrail.length - 0.5) * 2));
      line(xtrail[i], ytrail[i], xtrail[i+1], ytrail[i+1]);
    }
    stroke(255);
    //ellipse(x, y, 5, 5);
  }

  private void shiftTrail() {
    for(int i = 0; i < xtrail.length - 1; i++) {
      xtrail[i] = xtrail[i + 1];
      ytrail[i] = ytrail[i + 1];
    }
  }
}

void setup() {
  size(width, height);
  for(int i = 0; i < critters.length; i++) {
    critters[i] = new Critter(
      //random(0, width),
      //random(0, height),
      width/2,
      height/2,
      vel,
      random(0, TWO_PI));
  }

  // run the simulation off-screen for a bit
  // so it starts interestingly
  // (also interesting if you don't do this)
  for(int t = 0; t < traillength * 4; t++) {
    for(int i = 0; i < critters.length; i++)
      critters[i].update(1.0 / frameRate);
  }

  last_tick = millis();
}

float last_tick = 0;
void draw() {
  background(0);
  float tick = millis();
  for(int i = 0; i < critters.length; i++) {
    critters[i].update((tick - last_tick) / 1000.0);
    critters[i].draw();
  }
  last_tick = tick;
}

float angleshift = 0;
void keyPressed() {
  angleshift += PI;
  for(int i = 0; i < critters.length; i++)
    critters[i].angle = angleshift;
}

// I could build a k-d tree every frame
// to make this logular instead of linear!
// but I'm also lazy
Critter nearest(Critter critter) {
  // make sure there's a least one other critter
  if(critters.length < 2) return null;

  Critter c = critters[0];
  float d = dist(critter.x, critter.y, c.x, c.y);
  for(int i = 0; i < critters.length; i++) {
    if(critters[i] == critter)
      continue;
    float d2 = dist(critter.x, critter.y, critters[i].x, critters[i].y);
    if(d2 < d || c == critter) {
      d = d2;
      c = critters[i];
    }
  }
  return c;
}

// returns the relative difference between two angles
float anglediff(float zero, float angle) {
  float d = angle - zero;
  while(d < -PI) d += PI;
  while(d > PI) d -= PI;
  return d;
}

// returns which color a critter should be given their location
color col(float x, float y) {
  int level = (int) (y / height * colorlevels);
  //int level = (int) (x / width * colorlevels);
  if(level < 0) level = 0;
  if(level >= colorlevels) level = colorlevels - 1;
  return colors[level];
}

// scales a color by a factor
// (seems like something there's already a Processing function to do, somewhere)
color colscale(color a, float scale) {
  return color(red(a) * scale, green(a) * scale, blue(a) * scale);
}

Comments

Click here to view the comments on this post.