BOIDS : AN INTRODUCTION TO VISUALLY RECORDING HISTORY THROUGH CODING
ACKNOWLEDGMENTS Francesco Tacchini
INTRODUCTION Coding can be messy Coding is messy. It is a bunch of words that does not make grammatical sense. It even seems nonsensical! Yet we see the tangible results visually. Can we use it for creative purposes, after all that does seem to be the point of creating something that creates other things? The whole idea of using code to create something tangible in graphic design (especially) seems like an interesting place to start. However, what part of design could I look at? I will NOT be learning how to code or create my own but rather will be bastardising a code I have found on the Internet to create different interactive models that explore the realm of visually recording the history of a space. It will probably be very abstract.
THE CODE The original code is by Daniel Shiffman and is an implementation of Craig Reynoldâ&#x20AC;&#x2122;s Boids program to simulate the flocking behavior of birds. Each boid steers itself based on rules of avoidance, alignment, and coherence. Flock flock; void setup() { size(640, 360); flock = new Flock(); // Add an initial set of boids into the system for (int i = 0; i < 150; i++) { flock.addBoid(new Boid(width/2,height/2)); } } void draw() { background(50); flock.run(); } // Add a new boid into the System void mousePressed() { flock.addBoid(new Boid(mouseX,mouseY)); }
// The Boid class class Boid { PVector location; 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)); location = 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 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(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 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); } // 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(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 float theta = velocity.heading2D() + radians(90); // heading2D() above is now heading() but leaving old syntax until Processing.js catches up fill(200, 100); stroke(255); pushMatrix(); translate(location.x, location.y); rotate(theta); beginShape(TRIANGLES); vertex(0, -r*2); vertex(-r, r*2); vertex(r, r*2); endShape(); 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; } // 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â&#x20AC;&#x2122;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 < desiredseparation)) { // 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(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) { float neighbordist = 50; PVector sum = new PVector(0, 0); int count = 0; for (Boid other : boids) { float d = PVector.dist(location, other.location); if ((d > 0) && (d < neighbordist)) { 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(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 location (i.e. center) of all nearby boids, calculate steering vector towards that location PVector cohesion (ArrayList<Boid> boids) { float neighbordist = 50; 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 < neighbordist)) { 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); } } }
// 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); } }
THE SWARM THEORY (from wikipedia) Swarm intelligence (SI) is the collective behavior of decentralized, self-organized systems, natural or artificial. The concept is employed in work on artificial intelligence. The expression was introduced by Gerardo Beni and Jing Wang in 1989, in the context of cellular robotic systems. SI systems consist typically of a population of simple agents or boids interacting locally with one another and with their environment. The inspiration often comes from nature, especially biological systems. The agents follow very simple rules, and although there is no centralized control structure dictating how individual agents should behave, local, and to a certain degree random, interactions between such agents lead to the emergence of ‘intelligent’ global behavior, unknown to the individual agents. Examples in natural systems of SI include ant colonies, bird flocking, animal herding, bacterial growth, and fish schooling. The definition of swarm intelligence is still not quite clear. In principle, it should be a multi-agent system that has self-organised behaviour that shows some intelligent behaviour. The application of swarm principles to robots is called swarm robotics, while ‘swarm intelligence’ refers to the more general set of algorithms. ‘Swarm prediction’ has been used in the context of forecasting problems.
SWARM INTELLIGENCE MODEL Using the code as a base, the main obstacle to overcome was to find the variables that correspond to the behaviour of a single boid. Having identified them, it was just a matter of changing the variables to get different results. In both the models (swarm intelligence and attraction) it was the same variables being changed to get completely different results. The following were the variables changed in the code to allow for a faster and more reactive swarm; BEFORE AFTER
void mousePressed() { flock.addBoid(new Boid(mouseX,mouseY)); void mousePressed() flock.addBoid(new flock.addBoid(new flock.addBoid(new flock.addBoid(new flock.addBoid(new
{ Boid(mouseX,mouseY)); Boid(mouseX,mouseY)); Boid(mouseX,mouseY)); Boid(mouseX,mouseY)); Boid(mouseX,mouseY));
The above adjustment has changed the addition of a new boid to five new boids. Therefore, now the program adds five boids every time the mouse is pressed and it adds it to the location of the cursor present when the mouse is pressed. This accelerates the population increase in the swarm and therefore doesn’t take as long to create a more striking visual effect. BEFORE
location = new PVector(x, y); r = 2.0; maxspeed = 2; maxforce = 0.03;
AFTER
location = new PVector(x, y); r = 4.0; maxspeed = 10; maxforce = 1;
The above adjustments are as follows; ‘r’ refers to the size of the boids and they have been scaled up ‘maxspeed’ refers to the maximum speed the boids can travel at ‘maxforce’ refers to the force by which the boids either attract or repel each other Taking all these adjustments into consideration, the swarm is demonstrated at an accelerated rate.
SWARM CLUMPING MODEL This model was created in a similar fashion to the swarm intelligence model. It’s variables have been changed differently to its counterpart however the base code remains the same. The results however are very different. BEFORE AFTER
void mousePressed() { flock.addBoid(new Boid(mouseX,mouseY)); void mousePressed() flock.addBoid(new flock.addBoid(new flock.addBoid(new flock.addBoid(new flock.addBoid(new
{ Boid(mouseX,mouseY)); Boid(mouseX,mouseY)); Boid(mouseX,mouseY)); Boid(mouseX,mouseY)); Boid(mouseX,mouseY));
The above adjustment has changed the addition of a new boid to five new boids. Therefore, now the program adds five boids every time the mouse is pressed and it adds it to the location of the cursor present when the mouse is pressed. This accelerates the population increase in the swarm and therefore doesn’t take as long to create a more striking visual effect. BEFORE
location = new PVector(x, y); r = 2.0; maxspeed = 2; maxforce = 0.03;
AFTER
location = new PVector(x, y); r = 4.0; maxspeed = 10; maxforce = 10;
The above adjustments are as follows; ‘r’ refers to the size of the boids and they have been scaled up ‘maxspeed’ refers to the maximum speed the boids can travel at ‘maxforce’ refers to the force by which the boids either attract or repel each other Taking all these adjustments into consideration, the swarm is demonstrated at an accelerated rate and changes form. Not only do they behave erratically, but this erratic behaviour also
ADDITION OF THE ARDUINO The Arduino is a singleboard microcontroller that makes interactivity possible through simple code and sensors. It works through code that is loaded on its flash memory that is executed (according to the timing specified in the code—usually it is every 10 microseconds). Although, originally intended for prototyping, they have been adapted to purpose by many creative today. The addition of the arduino to the previous code allows me to control the input of new boids through an external sensor (Sonic Range Sensor) rather than a mouse click—thereby making the processing code interactive. This results in a visual graphic (a swarm of boids) that records the visual history of the space through the addition of new boids when a user passes through the space. RightThe arduino code that dictates the collection and transfer of data to processing. As you can see, the code is very similar to the one seen in processing.
int sensorPin = A0; // analog input pin to hook the sensor to int sensorValue = 0; // variable to store the value coming from the sensor // the setup routine runs once when you press reset: void setup() { // initialize the digital pin as an output. Serial.begin(9600); pinMode(sensorPin, INPUT); } // the loop routine runs over and over again forever: void loop() { sensorValue = analogRead(sensorPin); // read the value from the sensor if(sensorValue < 100){ //CAREFUL! this is the number that you have to change // If you want to change stay in the 0-1000 range. 0 being lowest force. Serial.println(sensorValue); delay(10); } }
1 2 1. An arduino programming interface. 2. An arduino â&#x20AC;&#x153;unoâ&#x20AC;? board for prototyping (the one that is used in this project) 3. A sonic range sensor that works by outputting a sonic frequency and capturing an echo.
3
CHANGES IN THE CODE Due to the addition of the arduino the code in processing had to be further changed in order to accommodate the code into one that is not blatantly interactive. There were two main things that changed in the code—the way new boids would be created and the number of boids that would be introduced. In the original code, new boids were created through mouse clicks and their position within the screen was dependent upon the position of the cursor. This changed to make way for a sonic sensor module. The code was changed so that instead of a mouse click, new boids would be created when the sensor virtually starts “printing”. The second thing that changed (or rather, reverted back to the original code) was the number of boids that is created with a single click (or detection of person passing underneath it). While previously, it was changed to 5 boids being created, this modification changed it back to a singular boid within the screen. This is because without this the computer would not be powerful enough to calculate all those variables in time and would result in a crash.
BEFORE
AFTER
void mousePressed() flock.addBoid(new flock.addBoid(new flock.addBoid(new flock.addBoid(new flock.addBoid(new
{ Boid(mouseX,mouseY)); Boid(mouseX,mouseY)); Boid(mouseX,mouseY)); Boid(mouseX,mouseY)); Boid(mouseX,mouseY));
void serialEvent (Serial myPort) { flock.addBoid(new Boid(width/2, height/2));
THE ARDUINO CODE Along with the changes in the code in processing the arduino code had to be modified too. The main modification being that of the time delay (i.e. the time periods that the sensors act within). The previous code read and printed the incoming data every 10 th of a second. This had to be changed to every 2 seconds. Another major change was the addition of an LED that activated when the sonic range sensor detected movement. BEFORE
AFTER
int sensorPin = A0; // analog input pin to hook the sensor to int sensorValue = 0; // variable to store the value coming from the sensor // the setup routine runs once when you press reset: void setup() { // initialize the digital pin as an output. Serial.begin(9600); pinMode(sensorPin, INPUT); } // the loop routine runs over and over again forever: void loop() { sensorValue = analogRead(sensorPin); // read the value from the sensor if(sensorValue < 100){ //CAREFUL! this is the number that you have to change // If you want to change stay in the 0-1000 range. 0 being lowest force. Serial.println(sensorValue); delay(10); } } int sensorPin = A0; // analog input pin to hook the sensor to int sensorValue = 0; // variable to store the value coming from the sensor int Light = 12; // the setup routine runs once when you press reset: void setup() { // initialize the digital pin as an output. Serial.begin(9600); pinMode(sensorPin, INPUT); pinMode(Light, OUTPUT); } // the loop routine runs over and over again forever: void loop() { sensorValue = analogRead(sensorPin); // read the value from the sensor if(sensorValue < 100){ //CAREFUL! this is the number that you have to change // If you want to change stay in the 0-1000 range. 0 being closest. Serial.println(sensorValue); digitalWrite(Light, HIGH); // turn the LED on (HIGH is the voltage level) delay(2000); } else { digitalWrite(Light, LOW); // turn the LED on (HIGH is the voltage level) } }
HOW IT IS INTER-CONNECTED 1
2
3
4
5
1.The code is written in the arduino coding interface and is verified and uploaded to the arduino uno. 2.The sensors are soldered and plugged into the arduino and the code is saved. It is now interactive and receiving input from the sensors and outputting data. 3.The data collected from the sensors is “printed” back through a serial port (the serial port being a Univeral Serial Bus aka USB) called a serial print. 4.This serial print data is exported into the processing coding interface. 5.When the processing code is run, the visual output is a boid swarm that interacts through live and external data.