CS 467 - Wandering Ants

Due: 2020-03-31 5:00p

Goals

  • Experiment with noise
  • Start solidifying our approach to "agents"
  • Explore your aesthetic sense through play

Prerequisites

  1. Accept the assignment on our Github Classroom.
  2. Download the git repository to your local computer.

Assignment

This practical is going to be closely related to our last one. Once again, you are going to do a random walk, but this time, rather than determining the direction purely at random, you are going to consult the noise() function based on where you are to determine where to go.

You will see that the behavior is quite different. The movement looks more purposeful, and two walkers who pass through the same location will head in the same direction. We start to get patters that look like river systems, with small feeders joining up with central trunks.

Practical 4 Goal

Links to the references for the included functions can be found at the bottom.

Part 1: Make an Ant

We are going to follow the same pattern as last time, creating an object to encode the information for an individual walker. This time, however, we will be more deliberate about it and give the walker its own agency (i.e., we will add functions). We will call our walker an "ant" because it is following somewhat ant-like behavior. It appears it be wandering randomly, but when it finds the scent trail of other ants, it will follow along in the same direction.

While we could make our ants use JavaScript object literals as we did for the last practical, this time we will get formal and define a class. Here is an example duplicated from the linked reference:

class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
  // Getter
  get area() {
    return this.calcArea();
  }
  // Method
  calcArea() {
    return this.height * this.width;
  }
}

const square = new Rectangle(10, 10);

console.log(square.area); // 100

Things to note are the constructor method, the use of the this keyword and way in which methods are defined.

Create the class

Create an Ant class. It should have two methods: the constructor and update.

In the constructor, add a new property called position and initialize it to a new vector. The x and y coordinates should be initialized to some random location on the canvas.

The update method

The update method will be responsible for handling the movement of the ant and drawing a trace of its movement.

The general theory is as follows: The ant will consult the 2D noise field at its current location. This will be a value from [0,1). This value will be used to set the heading of the ant by mapping it to a value between 0 and 2π. You will use this to create a direction vector, which you will add to the ant's current position, while drawing the line from the old position to the new (so quite similar to our random walk).

Now for the details.

Recall that the smaller the step we make in noise space, the more coherent the values are. We typically add a scaling factor to the values we pass to noise to provide control over this. To make it easier to tune the behavior, add a global const called noiseScale and set it to 0.03.

The first step of update is to read the noise value for the current location. Pass in the x and y components of the position, scaled with noiseScale.

Then, use the map function to convert it from the range [0,1) to [0 - 2π]. This will be the new heading for the ant.

One of the nice methods in p5.Vector is fromAngle, which allows you to create a new unit vector from an angle. Note that it takes a second optional argument that will set the length. You can use this to increase the distance the ant travels in a particular time step (try 2 to start with).

To get the ant's new location, we can add this direction vector to it's current location. Last time, because of the double rendering we were doing, we created a new vector to store the new location. This time we are going to get clever. Instead of using the line() function, we are going to draw the line with beginShape(LINES), vertex() and endShape(). The advantage of this is that we can specify the end points of the line one at a time. This gives us the opportunity to specify the first point using the ant's position vector, update the position in place, and then specify the destination using the position vector again.

Keeping the ant on screen

Because the ant is more purposeful than the random walkers we created last time, we really do need handle it walking off screen.

There are a number of ways to handle this, but we will add a wrap around (which you may have added on the last practical). If the positions x component exceeds width, set it to 0, and if it drops below 0, set it to width. Write this on the bottom of the update function and do the same for the y component.

Part 2: Set the ant loose

As before, we will draw a single ant to make sure it is working first. Create a new global variable to hold your ant. In setup, initialize the variable with new Ant(). Note that we can't initialize the ant when we declare the variable because the constructor relies on createVector, which isn't available until setup is called.

In the draw function, call the ant's update function. We want to see the trail left by the ant, so we won't call background in the draw function. If you want to set the color, you need to do it in the setup function.

Your ant should now meander across the screen. If you adjust the noise scale, you will be able to control how direct the path is.

Part 3: More ants!

The single ant is not that exciting. Things only really pick up when we add more ants and start to get interesting patterns through emergence.

As you did for the last practical, replace the single global variable with an array, and load it up with ants in the setup function. In the draw function, iterate through the ants and update each in turn. The above picture has a couple of thousand ants in it.

Aesthetically, I like to have the ants draw in white with a very low alpha value on black. This makes the points where the ants converge stand out more and makes a nice background where the individual ants started. You should play with this a little.

Part 4: "Killing" the ants

One of the things you will notice is that after a few moments, the picture stops changing very much. What happens is that the ants all eventually cross into one of the major thoroughfares and once on it, there is no way off, since they are subject to the directions in the noise space.

One way to keep the image evolving a little bit is to give the ants "life-spans". Add a new property to the Ant class called life. On every call to update, you will subtract from this variable. If the value hits zero, the ant "dies". Of course, we want to keep drawing, so we will resurrect the ant in a new random location and with a restored life-span.

Make the ant's life a random value. If they all have the same life-span, there will be obvious moments when all of the ants suddenly jump to a new location.

Part 5: Play

I would like you to play around with this a little bit and change it a little bit from the description above.

You should certainly play around a bit with color, noise scale, and the distance the ant's move per update cycle.

You could color by age, or you could keep track of how many times the ant as been resurrected and use that to control color.

You could add a little bit more chaos by using the third noise argument so that not all ants are following exactly the same directions. For example, I made an example where every time an ant was resurrected, it would advance to the next "layer" of 3D noise space (technically, the space is continuous, but if we keep a fixed step size, we can think of it as layers).

Experiment and see if you can make something that you find aesthetically pleasing.

Finishing up

Commit your changes to git and push them back up to GitHub. I will find them there.

References

Links to the reference pages for the functions you will be using:

background
beginShape
createCanvas
createVector
endShape
fromAngle
map
noise
random
stroke
p5.Vector
vertex