Ants

We spent some time in Nature of Code learning about Autonomous Steering Behaviors. This is a fancy umbrella term for simulations of group and individual movements “in the wild.” This can describe phenomena like the flocking of birds, or the way a crowd moves when shuffling into a doorway.

I combined the Wander and Separate examples to make a colony of ants. The overall movement feels almost right, but for some reason I could not get the ants’ bodies to angle in the direction of their movement. This is problematic because it makes them move backward as well as forward. This may have been caused by the way I constructed the ants in Processing–I started from the middle and worked my way out, working from the front or the back.

Main sketch code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
ArrayList <Vehicle> ants;
 
void setup()
{
  size(600, 600);
  smooth();
 
  ants = new ArrayList<Vehicle>();
  for (int i = 0; i < 50; i++)
  {
    ants.add(new Vehicle(random(width),random(height)));
  }
}
 
void draw()
{
  background(58, 188, 15);
  fill(6, 72, 32);
  noStroke();
  randomSeed(15);
  for(int j=0; j<300; j++)
  {
    rect(random(width), random(height), 2, 15);
  }
  fill(137, 252, 8);
  for(int j=0; j<300; j++)
  {
    rect(random(width), random(height), 2, 15);
  }
 
  for (Vehicle v : ants)
  {
    // Path following and separation are worked on in this function
    v.separate(ants);
    // Call the generic run method (update, borders, display, etc.)
    v.update();
    v.borders();
    v.display();
    v.wander();
  }
}

Vehicle (ant) class code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
 
class Vehicle {
 
  // All the usual stuff
  PVector location;
  PVector velocity;
  PVector acceleration;
  float r;
  float maxforce;    // Maximum steering force
  float maxspeed;    // Maximum speed
  float wandertheta;
 
    // Constructor initialize all values
  Vehicle(float x, float y) {
    location = new PVector(x, y);
    r = 12;
    maxspeed = 3;
    maxforce = 0.2;
    acceleration = new PVector(0, 0);
    velocity = new PVector(0, 0);
    wandertheta = 0;
  }
 
  void applyForce(PVector force) {
    // We could add mass here if we want A = F / M
    acceleration.add(force);
  }
 
  // Separation
  // Method checks for nearby vehicles and steers away
  void separate (ArrayList<Vehicle> vehicles) {
    float desiredseparation = r*2;
    PVector sum = new PVector();
    int count = 0;
    // For every boid in the system, check if it's too close
    for (Vehicle other : vehicles) {
      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 < desiredseparation)) {
        // Calculate vector pointing away from neighbor
        PVector diff = PVector.sub(location, other.location);
        diff.normalize();
        diff.div(d);        // Weight by distance
        sum.add(diff);
        count++;            // Keep track of how many
      }
    }
    // Average -- divide by how many
    if (count > 0) {
      sum.div(count);
      // Our desired vector is the average scaled to maximum speed
      sum.normalize();
      sum.mult(maxspeed);
      // Implement Reynolds: Steering = Desired - Velocity
      PVector steer = PVector.sub(sum, velocity);
      steer.limit(maxforce);
      applyForce(steer);
    }
  }
 
  void wander() {
    float wanderR = 25;         // Radius for our "wander circle"
    float wanderD = 40;         // Distance for our "wander circle"
    float change = 0.5;
    wandertheta += random(-change,change);     // Randomly change wander theta
 
    // Now we have to calculate the new location to steer towards on the wander circle
    PVector circleloc = velocity.get();    // Start with velocity
    circleloc.normalize();            // Normalize to get heading
    circleloc.mult(wanderD);          // Multiply by distance
    circleloc.add(location);               // Make it relative to boid's location
 
    float h = velocity.heading2D();        // We need to know the heading to offset wandertheta
 
    PVector circleOffSet = new PVector(wanderR*cos(wandertheta+h),wanderR*sin(wandertheta+h));
    PVector target = PVector.add(circleloc,circleOffSet);
    seek(target);
 
    // Render wandering circle, etc.
    //if (debug) drawWanderStuff(location,circleloc,target,wanderR);
  }
 // A method that calculates and applies a steering force towards a target
  // STEER = DESIRED MINUS VELOCITY
  void seek(PVector target) {
    PVector desired = PVector.sub(target,location);  // A vector pointing from the location to the target
 
    // Normalize desired and scale to maximum speed
    desired.normalize();
    desired.mult(maxspeed);
    // Steering = Desired minus Velocity
    PVector steer = PVector.sub(desired,velocity);
    steer.limit(maxforce);  // Limit to maximum steering force
 
    applyForce(steer);
  } 
 
 
  // Method to update location
  void update() {
    // Update velocity
    velocity.add(acceleration);
    // Limit speed
    velocity.limit(maxspeed);
    location.add(velocity);
    // Reset accelertion to 0 each cycle
    acceleration.mult(0);
  }
 
  void display() {
    fill(175);
    pushMatrix();
    translate(location.x, location.y);
    smooth();
    noStroke();
    fill(82, 40, 36);
    ellipse(location.x+20, location.y, 11, 11.5);  //body
    ellipse(location.x+10, location.y, 10, 8);
    ellipse(location.x, location.y, 12, 8);
    ellipse(location.x-13, location.y, 20, 13);
    stroke(82, 40, 36);
    strokeWeight(1);
    line(location.x+12, location.y, location.x+8, location.y-12);  //front legs
    line(location.x+12, location.y, location.x+8, location.y+12);
    line(location.x+8, location.y-12, location.x+20, location.y-8);
    line(location.x+8, location.y+12, location.x+20, location.y+8);
    line(location.x-4, location.y, location.x-8, location.y-12);  //bottom legs
    line(location.x-4, location.y, location.x-8, location.y+12);
    line(location.x-8, location.y+12, location.x-20, location.y+17);
    line(location.x-8, location.y-12, location.x-20, location.y-17);
    line(location.x+2, location.y, location.x+6, location.y-12);  //middle legs
    line(location.x+2, location.y, location.x+6, location.y+12);
    line(location.x+6, location.y-12, location.x-6, location.y-16);
    line(location.x+6, location.y+12, location.x-6, location.y+16);
    popMatrix();
  }
 
  // 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;
  }
}