So have two functions that can draw circles and squares, what if we what one function that is capable of drawing either? Well, this is simply a case of providing an argument that tells the function which type of figure to draw: again, the magic is in the function arguments.
Let us create a new function called ‘drawShape’ then, and have it accept and argument specifying the type of shape followed by the usual position and size arguments that we’ve been using above; we’ll end up with a function like this:
void drawShape( int shapeType, float x, float y, float width ) {
// The code to do the drawing
}
We’ll use ‘width’ as the generic size since it covers the diameter of the circle well also.
We come to a slight stumbling block here though – what do we pass as an argument for ‘shapeType’? The common method for answering this is to define some constant values that are agreed upon and defined globally at the top of the file, so we could have:
int doCIRCLE = 1;
int doSQUARE = 2;
and then our function will be:
void drawShape( int shapeType, float x, float y, float width ) {
if ( shapeType == doCIRCLE ) {
drawCircle(x,y,width);
}
if ( shapeType == doSQUARE ) {
drawSquareCentred(x,y,width);
}
}
If we then modify our setup routine as follows, we find that we get exactly the same result as before.
void setup(){
size(400,400);
background(255);
drawShape(doSQUARE,200,200,150);
drawShape(doCIRCLE,200,200,150);
}
If, at a later date you were to write a function to draw an octagon, then it would be very simple to add that as a further option to the drawShape function by defining a new constant value, eg doOCTAGON. This would then mean that everywhere that you had used drawShape would immediately be able to take advantage of the extra shape without a need to modify the code in each place where it was used.
This is a very powerful aspect of functions and the design of the argument list. It should be noted, however, that a single function should be limited to performing a specific task – if a function is designed to draw a shape, do not then expand it to also calculate some complex mathematical sum on a dataset also, that should be the job for a different function. Sometimes it is all too tempting to add in a bit of extra functionality because ‘it’s doing something else with the same data’, but this generally just leads to difficult to track down bugs and very hard to understand code.
A further useful aspect of functions is their ability to make calls to themselves! This is called ‘recursion’. It is a technique that should generally be avoided, but in controlled situations can be very useful. The reason for its avoidance is that is can very quickly use up a lot of processing memory if the number of recursions is not monitored in some way.
The example we are going to look at uses a ‘depth’ system to keep the recursion in check. This example will show a method of drawing a grid of circles specified by a depth argument… the depth indicates how many multiples of two are applied to the grid along each side, so a depth of 1 would give a 2 by 2 square, a depth of 2 would give a (2 x 2) by (2 x 2) square and so on. We’ll call this function ‘drawCircles’, and you will see when you enter the function the first check is to see if we need to go any deeper before starting to draw: if we do, then we split the area up into quartiles and make a further call for each of the four quartiles with depth reduced by one… this goes on until there’s no need to go any deeper and so a circle is drawn and the function returns either to the original called program or to the next place within the drawCircles function that it had got up to before.
void drawCircles(float x, float y, float diameter, int depth) {
if ( depth > 0 ) {
float halfDiameter = diameter / 2 ;
drawCircles(x - halfDiameter, y - halfDiameter, halfDiameter, depth - 1) ;
drawCircles(x + halfDiameter, y - halfDiameter, halfDiameter, depth - 1) ;
drawCircles(x - halfDiameter, y + halfDiameter, halfDiameter, depth - 1) ;
drawCircles(x + halfDiameter, y + halfDiameter, halfDiameter, depth - 1) ;
}
else {
drawCircle(x,y,diameter);
}
}
It can be seen from this function declaration that drawCircles is called exactly the same as drawCircle, but with the addition of a ‘depth’ argument.
A setup as follows produces the geometric pattern shown:
void setup(){
size(400,400);
background(255);
fill(229,138,19);
drawSquareCentred(200,200,150);
fill(229,19,19);
drawCircles(200,200,150,1);
fill(169,19,229);
drawCircles(200,200,150,2);
fill(56,19,229);
drawCircles(200,200,150,3);
fill(19,229,227);
drawCircles(200,200,150,4);
fill(46,229,19);
drawCircles(200,200,150,5);
}