Second Tutorial: Arrays and Objects with an agent as example.
In the first tutorial we had a look at basics notions of programming such as syntax, types, variables, control flow, scope and functions. We are going to look at two more basic concept in this tutorial, which are Arrays and Classes, and illustrate them with a first example often used in generative design, as an agent or particle.
Our Particle.
First, I will introduce the code for the particle that we will use as an example. A particle represents a position in space and possibly other attributes such as velocity, direction or mass. At each iteration (for example at each call to draw()) the position of the particle will be updated. We are going to use a particle in 2D, which has a velocity vector . A vector is a geometric quantity having a magnitude and a direction. In this case, since it is a 2D vector, it will have an x and y component, which we are going to define through variables vx and vy.
float posx, posy; //position of particle float vx, vy; //velocity vector void setup(){ size(500,500); //we use ellipseMode to draw the ellipses from //the centre. Check reference if you want to know more. ellipseMode(CENTER); noFill(); //we don't want them filled //here we use the random function to generate //a random number within a range. We use also //width and height to get the screen dimensions posx=random(0,width); posy=random(0,height); vx=random(-2,2); vy=random(-2,2); } void draw(){ background(255); //draw the background, white stroke(0,100,255); ellipse(posx, posy, 20,20); stroke(0,0,0); //draw vx and vy, from posx and posy, 10 times bigger line(posx,posy,posx+vx*10,posy+vy*10); //AND NOW WE UPDATE THE POSITION posx += vx; posy += vy; //this is what it will do when it reaches an edge of the screen bounceOnScreenEdges(); } //here we reset the velocity void bounceOnScreenEdges(){ if(posx>width || posx<0){ vx=-vx; } if(posy>height || posy<0){ vy=-vy; } }
Arrays.
This is all fine and fun, but what happens if we want to have many particles (or for that matter many things of the same type)? do we have to define a variable for each particle? luckily enough there are arrays for this. An array is similar to a variable, in that it has a type, but instead of only one instance of that variable it has many, which we can address independently through an index. The syntax of an array in processing (and Java) is this:
float[] numbers = new float[10];
This means that the type of “numbers” is an array of floats ( “float[]” ). Then we create the array with “new float[]”, with the size we want. The size of the array cannot be changed. The array above will have 10 elements, and their indexes will range from 0 to 9 (arrays in most languages always start with an index of 0). To access any element of the array we use its indices, like so:
float[] numbers = new float[10]; //this is how we can access each element by their index numbers[1]=10; numbers[2]=5; numbers[0]=23.5; numbers[7]=3789.54; //this won't work, (no element 10) comment it off to see the what happens //numbers[10] = 6.5; println("the third element is: " + numbers[2]); //and this is how we iterate through all elements of the array for(int i=0; i< numbers.length; ++i){ println("element "+ i + " is " + numbers[i]); }
Now we can rewrite our agent/particle code, so there are many of them:
int nParticles=1000; //these are the array declarations float[] posx, posy; //position of particle float[] vx, vy; //velocity vector void setup(){ size(500,500); ellipseMode(CENTER); noFill(); //we don't want them filled //we allocate the arrays here: posx=new float[nParticles]; posy=new float[nParticles]; vx=new float[nParticles]; vy=new float[nParticles]; //and here we initialise its values for(int i=0;i<nParticles;++i){ //nParticles and the .length will be the same posx[i]=random(0,width); posy[i]=random(0,height); vx[i]=random(-1,1); vy[i]=random(-1,1); } } void draw(){ background(255); //draw the background, white stroke(0,100,255); for(int i=0;i<nParticles;++i){ ellipse(posx[i], posy[i], 10,10); } stroke(0,0,0); for(int i=0;i<nParticles;++i){ line(posx[i],posy[i],posx[i]+vx[i]*2,posy[i]+vy[i]*2); } //AND NOW WE UPDATE THE POSITION for(int i=0;i<nParticles;++i){ posx[i] += vx[i]; posy[i] += vy[i]; } for(int i=0;i<nParticles;++i){ bounceOnScreenEdges(i); } //and for fun, we convert it all into Brownian motion, //by adding random to the velocity: for(int i=0;i<nParticles;++i){ vx[i]+=random(-0.1,0.1); vy[i]+=random(-0.1,0.1); } } //here we reset the velocity void bounceOnScreenEdges(int index){ if(posx[index]>width || posx[index]<0){ vx[index]=-vx[index]; } if(posy[index]>height || posy[index]<0){ vy[index]=-vy[index]; } }
As an exercise (exercise 1), you can try to do this: you may observe that some particles accelerate too much after a while…
Can you write a function similar to “bounceOnScreenEdges” that iterates through each velocity component (x and y), checks if it is larger than a certain maximum value, and if it is, that it makes it equal to that maximum value?
And (exercise 2): by knowing that the size of a vector is the square root of the sum of the square of its components (size=sqrt(vx*vx + vy*vy)), can you do the same test as in exercise 1 but for the size of the vector? (this requires you to know some basic vector algebra, specially how to normalise a vector).
Objects and Classes.
An important concept in processing, and indispensable in Java, are classes. Classes are the basis of what it is known as Object Oriented Programming (OOP). The Class mechanism can be described as a way of describing our own types, besides the basic ones (bool, int, char, float…) that we have seen before. Classes can include variables and functions. The details of OOP are two complex to explain in this tutorial, but if you are really interested in learning about inheritance, interfaces and other aspects of OOP you can follow the link above. We are however going to look at a way of defining a basic class in Processing, using our agent or particle example. First we need to define our new “type” or class. We do this by writing class followed by the name we want to give to our class, and its body (its code) being defined by and opening and closing curly brackets. We call this the class declaration. Inside the class we can have any types of variables (even other classes), generally referred as its “fields”, and functions, called “methods”. This declaration of our class is only a sort of template, a description of an eventual “variable” of our class. A “variable” of our class is called an object, and it is declared as any other variable, with its type first (the name of our class) and a name we want to give it. We can access its methods and fields with a “.” operator. See the code below:
class Particle{ //these are the variables (called fields) float x,y,vx,vy; //variables can also be initialised float maxSpeed=5; //this is a constructor Particle(){ x=random(0,width); y=random(0,height); vx=random(-1,1); vy=random(-1,1); } //these are the functions (called "methods") void bounceOnScreenEdges(){ if(x>width || x<0){ vx=-vx; } if(y>height || y<0){ vy=-vy; } } void updatePosition(){ x += vx; y += vy; } void wiggle(){ vx+=random(-0.2,0.2); vy+=random(-0.2,0.2); checkMaxSpeed(); //class methods can be called with the class } void checkMaxSpeed(){ float velSizeSquare=vx*vx+vy*vy; //calculating square roots is not very efficient, //so we use the squares to compare...just a trick //of the trade. if(velSizeSquare>maxSpeed*maxSpeed){ //we normalise the direction float velSize=sqrt(velSizeSquare); vx=(vx/velSize)*maxSpeed; vy=(vy/velSize)*maxSpeed; } } void draw(){ stroke(0,100,255); ellipse(x, y, 10,10); stroke(0,0,0); line(x,y,x+vx*2,y+vy*2); } } //this is the end of the Particle class //declaration of our Particle Particle myParticle; void setup(){ size(500,500); ellipseMode(CENTER); noFill(); //we don't want them filled myParticle=new Particle(); } void draw(){ background(255); //draw the background, white myParticle.draw(); myParticle.bounceOnScreenEdges(); myParticle.wiggle(); myParticle.updatePosition(); }
As we have said, it is possible to have classes inside classes. In Java everything has to be classes (no functions or variables can be declared outside a class), so classes are always inside classes.If converted to Java, all Processing code will be included within a class, called the PApplet (you can check the Java code that your processing Sketch generates by exporting it (with the “Export Application” button, or in “File->Export Application” menu, that is, if you are in Java mode, which is the default), and checking the .java file it generates, which is inside a folder called “source”). It is possible to make arrays of objects as of any other type. The objects of the array need to be initialised, by iterating through the array and using the “new” operator and the constructor. For example, if we declare an array of Particle called myParticles, with:
Particle[] myParticles;
we need to initialise it like this:
myParticles=new Particle[1000]; for(int i=0;i<myParticles.length; ++i){ myParticles[i]=new Particle(); }
So now, (exercise 3), can you re-write the array of particle to use objects of the class Particle?
Values and References.
A Final important notion in Processing, and also present in many programming languages, is that of references. In Processing and Java all basic types (bool, char, int, float…) are always passed by value. What does this mean? this means that every time that we assign a variable from another, or pass a value to a function, the value of this variable is copied. If you write something like this for any of the basic types:
int a=10; int b; b=a; b++; println("a: "+ a); println("b: "+b);
The results will be:
a: 10 b: 11
As you see we have declared a variable a of type int, and we have set it to 10. We have created another variable b of type int, and we have set it to a, which means that it has copied the value stored in a, into b. If we increase b by one (through the ++ operator), it will change the value of b, but not of a. This seems obvious. But now lets do this instead:
//here we make a test class, called Foo class Foo{ int val; } Foo a=new Foo(); a.val=10; Foo b; b=a; b.val++; println("a: "+ a.val); println("b: "+ b.val);
The results will instead be:
a: 11 b: 11
This is strange, isn’t it? Actually any variable that we declare through “new”, such as objects or arrays, will have the same behaviour. What it is happening is that when we say “b=a;” we are not saying “copy the values of a into b”, but rather make b refer to a. a and b are actually the same object, but with two different names. This may sound problematic, but it is actually really useful. Here is an example:
void setup(){ float[] numbers=new float[5]; printArray(numbers); randomiseArray(numbers, 10,20); printArray(numbers); } void randomiseArray(float[] theArray, float min, float max){ for(int i=0;i<theArray.length; ++i){ theArray[i]=random(min, max); } } void printArray(float[] theArray){ for(int i=0;i<theArray.length; ++i){ println("element " + i +" is: "+ theArray[i]); } println(); }
The result of this is:
element 0 is: 0.0 element 1 is: 0.0 element 2 is: 0.0 element 3 is: 0.0 element 4 is: 0.0 element 0 is: 14.593256 element 1 is: 13.864506 element 2 is: 15.577421 element 3 is: 19.703238 element 4 is: 14.547775
Instead of copying the whole array when we pass “numbers” to “randomiseArray()” we copy a reference to it… the “float[] theArray” parameter in “randomiseArray()” is then a reference to “numbers”, and not a copy. You will see how this makes more sense as you use it while you program, in the rest of the course.
A swarm algorithm.
And to finish the tutorial, we are going to use our particle/agents to implement a classic example of self organisation: a swarm. The swarm or flocking algorithm we are going to use is based on the one first proposed by Craig Reynolds, called Boids. Reynolds’ algorithm is paradigmatic of the phenomena know as self-organisation and emergent behaviour, that is, the appearance of pattern, global organisation or form as the result of simple interactions between lower level components. The swarming of birds or schooling of fish, rather than explainable by some top-down rules, can be described as the result of the bottom-up coordination of the individual behaviour of each fish, bird, or agent. In the case of Boids, swarming is the outcome of 3 simple rules that each particle follows:
- Alignment: try to go on the same direction as your closest neighbours.
- Cohesion: try to go towards the average position of your neighbours.
- Separation: Avoid colliding and crowding with your closest neighbours.
In the code below, this is implemented in the function “swarm()”. The example also gives answer to exercise 3, since it uses an array of particles… the code of particles is slightly modified also (we don’t call “wiggle()”, so I have deleted it to make it clearer). And I have added a “wrapAroundUniverse()” method to the particles. It uses the % (modulo) operator, which is used to calculate the remainder of a division, to make that the particles that exit at one end of the screen, appear in another. For example, if the x position of a particle is the width of the screen + 10, its x will be set to 10, instead. We do a similar thing if its position is smaller than 0, so the screen “wraps around”.
class Particle{ float x,y,vx,vy; float futureVx,futureVy;//this added float maxSpeed=1; Particle(){ x=random(0,width); y=random(0,height); vx=futureVx=random(-1,1); //a way to initialise both at the same time... vy=futureVy=random(-1,1); normaliseSpeed(); } void wrappAroundUniverse(){ if(x>=0){ x = x % width; } else{ x=width+x;//x is negative... } if(y>=0){ y = y % height; } else{ y=height+y;//y is negative... } } void update(){ //update position. x += vx; y += vy; //and velocity vx=futureVx; vy=futureVy; normaliseSpeed(); wrappAroundUniverse(); } void normaliseSpeed(){ //this makes sure that the speed is always 1 float velSize=sqrt(vx*vx+vy*vy); vx=(vx/velSize)*maxSpeed; vy=(vy/velSize)*maxSpeed; } void draw(){ strokeWeight(1);//thickness of line stroke(0,100,255); ellipse(x, y, 5,5); stroke(0,0,0); line(x,y,x+vx*5,y+vy*5); } } //this is the end of the Particle class //declaration of the Particle array Particle[] particles; void setup(){ size(500,500); ellipseMode(CENTER); noFill(); //we don't want them filled particles=new Particle[500]; for(int i=0;i<particles.length; ++i){ particles[i]=new Particle(); } } void draw(){ background(255); //draw the background, white //we need first to calculate all the future velocities for(int i=0;i<particles.length; ++i){ swarm(particles[i]); } //and then update them and the position for(int i=0;i<particles.length; ++i){ particles[i].update(); particles[i].draw(); } } //this is a simplified Swarming behaviour based on the original //Boids by Craig Reynolds: http://www.red3d.com/cwr/boids/ void swarm(Particle me){ //these are the values we need to calculate, according to the algrithm described by //Craig Reynolds float averageVx=0, averageVy=0; //average Velocitites float relativeAveragePosx=0, relativeAveragePosy=0; //average Position float tooClosex=0, tooClosey=0; // in case any other particle is too close int numberOfNeighbours=0; int tooCloseNeighbours=0; //we go through all the particles for(int i=0;i<particles.length; ++i){ Particle other=particles[i]; if(other==me){ //if the other refers to the same particle as me (it is me): //this means to continue the current for loop, and ignore anything below. continue; } //now calculate the difference between me and other float difx=other.x-me.x; float dify=other.y-me.y; //square distance (typical in vectors...) float disSquare= difx*difx + dify*dify; //now we check if "other" is close enough, or if we will also ignore it float minDistance=60; if(disSquare>minDistance*minDistance){ //if further than minDistance, ignore continue; } //now we know that "other" is not "me" and it is close enough... ++numberOfNeighbours; averageVx+=other.vx; averageVy+=other.vy; relativeAveragePosx+=difx; relativeAveragePosy+=dify; float avoidDistance=10; if(disSquare<avoidDistance*avoidDistance){ //if it is too close ++tooCloseNeighbours; tooClosex+=difx; tooClosey+=dify; } }//this is the end of the for loop //now we have gone through all the particles, so , if //"me" had any neighbours, we add their effect (according to //the flocking algorithm) if( numberOfNeighbours>0){ averageVx /= numberOfNeighbours; //divide averageX by numberOfNeighbours averageVy /= numberOfNeighbours; relativeAveragePosx /= numberOfNeighbours; relativeAveragePosy /= numberOfNeighbours; if(tooCloseNeighbours>0){ tooClosex /= tooCloseNeighbours; tooClosey /= tooCloseNeighbours; } //change my velocity. We use some weighting values... float vW=0.05, posW=0.0001, repelW=0.01; me.futureVx = me.vx + vW*averageVx + posW*relativeAveragePosx - repelW*tooClosex; me.futureVy = me.vy + vW*averageVy + posW*relativeAveragePosy - repelW*tooClosey; } }
Since code is getting a bit long now, you can download the Processing project from: SwarmCode.
We have cover quite a few concepts and a new algorithm. For next time you should go through the material we have looked at today, and check the exercises. You may need to go back and refresh some of the things we looked at in first tutorial. Try to understand the code. When you don’t know what something does, or you have any doubts about if something may be written differently, or if it is at all allowed, don’t think, just change the code and see what happens! it is the most effective way of understanding the concepts.
You can for example compare this code with the implementation of flocking here.
Addendum: using PVector.
If you have been struggling with the vector maths of the particle example, there is a PVector class in Processing for doing vector arithmetic (normalising the vector, adding and subtracting… ). It may be however a bit complicated to use, because even if the math bits are easier, the programming aspects, particularly the Object Oriented related code, may be more complicated. You can try to write the examples above, using PVector. Here is the last code for the swarm written using PVector instead, you can see how it compares: SwarmWithPVector. There is a tutorial about PVector at the processing site, here.
In general, there are also a number of tutorials around that can be quite interesting to follow, so you may get another explanation of the same concepts I explain here (sort of a second opinion…). The above linked PVector tutorial is at the Processing website, under tutorials. There is also an interesting one about Object Oriented Programming, among other things.