Wednesday 11 March 2009

1,900 words - is that enough?

The vertex() function is a flexible way to draw lines and connect them into interesting shapes and patterns which can be repeated and varied however you like. Once you know how to repeat sections of code in a loop, use variables to change some characteristics of the line/shape, and apply trigonometry, even in a very basic way with limited understanding (as I have), then you can make some pleasing drawings with the vertex() function.
Vertices, as you may well know, are points in space which, taken together, define the shape and placing of an object, such as a line, triangle or polygon. A triangle has three vertices which define it’s corner points, and when joined together by lines, they make up the triangle shape. A polygon has n vertices, where n can be any number, and usually has equal length sides and equal internal angles. Trigonometry is the best and easiest way to define the vertices of a triangle or polygon, but don’t worry – I’ll show you how this can be done without going into technicals.
Since a vertex is a point in space, it follows that the vertex() function takes two arguments for the coordinates (or three if you are working in 3D):
vertex(x, y) (or vertex(x, y, z))
I’ll stick to 2D. Now, since vertices are used in processing to draw lines and shapes, we first of all want several of them – that’s easy, just add more calls to the vertex() function – and second of all we want to tell the compiler that they should be connected together. We do this by wrapping calls to vertex() in between calls to beginShape() and endShape() (in fact, vertex() can only be used in this way). beginShape() can take an argument defining how the vertices should be joined, such as POINTS, LINES or TRIANGLES, but I’ll just use the default (no arguments) mode, which connects vertices in a filled line strip. In other words, the first vertex is connected by a line to the second vertex, which is connected by another line to the third vertex, and so on ad finitum, and the shape is filled with colour. To generate an unfilled line strip, simply call noFill() somewhere in your code before beginShape(). endShape() can be passed the CLOSE argument, which closes the loop of vertices you have created to make a complete shape.
That should be all you need to start experimenting with the vertex() function. Things get more interesting though if you’re willing to dabble in a little trigonometry. Trigonometry is essential for drawing polygonal shapes with the vertex() function, and you don’t have to be a master to use it to draw some simple shapes. As you’ll probably soon see, my grasp of trigonometry is sketchy at best, but I’ve managed to use it to draw my sample program, which I’ll talk you through now.
My program draws shrinking polygons inside each other, with increasing numbers of sides and decreasing colour from black to white, vanishing to a point in the centre. The same basic structure is repeated on different parts of a coloured background to give a wallpaper effect (I’m thinking more computer screens than actual walls). The image is rendered below:




The program starts with the following setup code:
size(700, 600);
background(0, 255, 255);
noFill();
strokeWeight(2);
smooth();
float x = 0, y = 0;
int noShapes = 100;
Most of this should be familiar already. Notice the call to noFill() before I have called beginShape(). One thing you probably won’t have seen is the use of smooth(). Calling this function applies anti-aliasing to the drawing, which gives a smoother look to edges and lines. It’s quite complicated how this works, suffice to say that some algorithim is applied which makes sensible estimates of pixel colour where lines and edges cross a pixel in a diagonal or curved way, so that the line is part inside/part outside. Pixels only some way in/out of the edge are assigned a colour that represents the degree of that pixel’s overlap with the edge. A simple technique would be to apply a colour equal to the edge colour multiplied by the percent of overlap, but a more popular (and more processor intensive) technique is supersampling. In this method, an image is rendered to a pixel buffer in memory that is of far higher resolution than the actual screen, so that each screen pixel is represented in the buffer by a larg(ish) array of buffer pixels. As the image is mapped back to the resolution of the screen, the colours assigned to screen pixels are averages of the colours in the equivalent buffer pixels.
Anti-aliasing is a powerful although processor intensive technique that can dramatically improve a picture’s rendering. Here is what my wallpaper sketch looks like without the call to smooth():


This is the code for the basic shape:
float angle = 0;
for (int i=3; i<=noShapes; i++){
stroke(i*2.55, i*2.55, i*2.55);
beginShape();
strokeJoin(MITER);
for (int j=0; j
x = width/2+cos(radians(angle))*width/i-2;
y = height/2+sin(radians(angle))*width/i-2;
vertex(x, y);
angle += 360/i;
}
endShape(CLOSE);
}
Apart from the trigonometry used in specifying the vertices, there’s one other new function above that I’ll quickly draw your attention to. The strokeJoin() function takes an argument that describes how lines are joined together to form end caps. The possible arguments are MITER, which is the default and gives a pointed cap, BEVEL, which gives a squared-off cap and ROUND, which gives a rounded cap. This is very similar to the strokeCap() function which does the same job for the end caps of straightforward lines.
Now I’ll dissect the specifications of the vertices, x and y, which use trigonometry based on the unit circle. The essential point about trigonometry is that the location of any point on a circle can be found by using an angle of rotation and the radius of the circle. There is a general statement for each of the x and y coordinates that can be used to do this:
x coordinate = x position of the centre of the circle + cos(angle of rotation in radians) * radius
y coordinate = y position of the centre of the circle + sin(angle of rotation in radians) * radius
The functions sin and cos are ratios of side lengths in a right-angled triangle applied to one of the internal angles:
Sin(Ѳ) = length of side opposite Ѳ / length of longest side
Cos(Ѳ) = length of side adjacent to Ѳ / length of longest side
The longest side in a right-angled triangle is always the side opposite the right-angle itself, and is called the hypotenuse. The hypotenuse corresponds to the radius of the circle with one end at the centre and the other end located somewhere on the circumference.
The other thing you need to know is that the unit circle is described in a polar coordinate system measured in radians, not the Cartesian system, so we need to converte our angle measurements from Cartesian degrees to polar radians before making use of sin and cos. The radians() function in Processing does this for us. The actual conversion is:
angle in radians = (angle in degrees) * π / 180
Since polygonal shapes fit nicely within a circle, they can be specified easily by locating their vertices as equal spacings along the circumference of a circle. You can see that the specifications of the x and y coordinates in my program fit the general form for finding the location of a point on a circle described above. However, it slightly complicates matters that they are specified within a double loop. So what effect does this have?
It has two effects. The outer loop tells us the number of shapes (all polygons) that are drawn in the pattern; every iteration of this loop draws one shape, and there are (noShapes – 3) iterations. The inner loop tells us how many sides there are to the shape (remember I am using endShape(CLOSE) which means I can draw one less side by having the stopping condition j < i as opposed to j <= i, and have Processing close the shape for me – it doesn’t matter whether I do it this way or draw the extra side through a further iteration of the loop).
The number of sides is determned by the value of the outer loop counter. So starting with 3 sides, the next shape has four sides, then five, and so on up to noShapes. At the same time, the sizes of the polygons are shrinking; this is achieved by decreasing the radius of the specifying unit circle with each pass through the loop (I divide the width of the screen by i-2, where i is the outer loop counter, to give the radius).
I have kept the shapes regular polygons by adjusting the angle of rotation passed to sin and cos in a uniform way within the inner loop. Since i tells us the number of sides to the polygon, it also gives us the ratio of angles to be passed to sin and cos for the next vertex. angle += 360/i gives the next angle for the next set of vertices in a regular polygon with i sides.
Finally, the stroke weight increases by a factor of 2.55 with each new polygon shape, giving a grading effect from dark to light. The above section of code centres the shrinking polygons pattern in the middle of the screen by defining the centre of the unit circle to be (width / 2, height / 2). This basic pattern is easily repeated in other parts of the screen by relocating the centre of the defining unit circle.
The full code listing is given here:
size(700, 600);
background(0, 255, 255);
noFill();
strokeWeight(2);
smooth();
float x = 0, y = 0;
int noShapes = 100;

float angle = 0;
for (int i=3; i<=noShapes; i++){
stroke(i*2.55, i*2.55, i*2.55);
beginShape();
strokeJoin(MITER);
for (int j=0; j<=i; j++){
x = width/2+cos(radians(angle))*width/i-2;
y = height/2+sin(radians(angle))*width/i-2;
vertex(x, y);
angle += 360/i;
}
endShape(CLOSE);
}

for (int i=3; i
stroke(0+i*2.55, 0+i*2.55, 0+i*2.55);
beginShape();
strokeJoin(ROUND);
for (int j=0; j
x = width/1.1+cos(radians(angle))*width/i-2;
y = height/2+sin(radians(angle))*width/i-2;
vertex(x, y);
angle += 360/i;
}
endShape(CLOSE);
}
for (int i=3; i
stroke(0+i*2.55, 0+i*2.55, 0+i*2.55);
beginShape();
strokeJoin(ROUND);
for (int j=0; j
x = width/2+cos(radians(angle))*width/i-2;
y = height/1.1+sin(radians(angle))*width/i-2;
vertex(x, y);
angle += 360/i;
}
endShape(CLOSE);
}
for (int i=3; i
stroke(0+i*2.55, 0+i*2.55, 0+i*2.55);
beginShape();
strokeJoin(ROUND);
for (int j=0; j
x = width/1.1+cos(radians(angle))*width/i-2;
y = height/1.1+sin(radians(angle))*width/i-2;
vertex(x, y);
angle += 360/i;
}
endShape(CLOSE);
}
for (int i=3; i
stroke(0+i*2.55, 0+i*2.55, 0+i*2.55);
beginShape();
strokeJoin(ROUND);
for (int j=0; j
x = width*0.09+cos(radians(angle))*width/i-2;
y = height/2+sin(radians(angle))*width/i-2;
vertex(x, y);
angle += 360/i;
}
endShape(CLOSE);
}
for (int i=3; i
stroke(0+i*2.55, 0+i*2.55, 0+i*2.55);
beginShape();
strokeJoin(ROUND);
for (int j=0; j
x = width/2+cos(radians(angle))*width/i-2;
y = height*0.09+sin(radians(angle))*width/i-2;
vertex(x, y);
angle += 360/i;
}
endShape(CLOSE);
}
for (int i=3; i
stroke(0+i*2.55, 0+i*2.55, 0+i*2.55);
beginShape();
strokeJoin(ROUND);
for (int j=0; j
x = width*0.09+cos(radians(angle))*width/i-2;
y = height/1.1+sin(radians(angle))*width/i-2;
vertex(x, y);
angle += 360/i;
}
endShape(CLOSE);
}
for (int i=3; i
stroke(0+i*2.55, 0+i*2.55, 0+i*2.55);
beginShape();
strokeJoin(ROUND);
for (int j=0; j
x = width/1.1+cos(radians(angle))*width/i-2;
y = height*0.09+sin(radians(angle))*width/i-2;
vertex(x, y);
angle += 360/i;
}
endShape(CLOSE);
}
for (int i=3; i
stroke(0+i*2.55, 0+i*2.55, 0+i*2.55);
beginShape();
strokeJoin(ROUND);
for (int j=0; j
x = width*0.09+cos(radians(angle))*width/i-2;
y = height*0.09+sin(radians(angle))*width/i-2;
vertex(x, y);
angle += 360/i;
}
endShape(CLOSE);
}


Tuesday 10 March 2009

A start on my chunk on vertex() function

The vertex() function is a flexible way to draw lines and connect them into interesting shapes and patterns which can be repeated and varied however you like. Once you know how to repeat sections of code in a loop, use variables to change some characteristics of the line/shape, and apply trigonometry, even in a very basic way with limited understanding (as I have), then you can make some pleasing drawings with the vertex() function.

Vertices, as you may well know, are points in space which, taken together, define the shape and placing of an object, such as a line, triangle or polygon. A triangle has three vertices which define the points in space, and when joined together by lines, they make up the triangle shape. A polygon has n vertices, where n can be any number, and usually has equal length sides and equal internal angles. Trigonometry is the best and easiest way to define the vertices of a triangle or polygon, but don’t worry – I’ll show you how this can be done without going into technicals.

Since a vertex is a point in space, it follows that the vertex() function takes two arguments for the coordinates (or three if you are working in 3D):

vertex(x, y) (or vertex(x, y, z))

I’ll stick to 2D. Now, since vertices are used in processing to draw lines and shapes, we first of all want several of them – that’s easy, just add more calls to the vertex() function – and second of all we want to tell the compiler that they should be connected together. We do this by wrapping calls to vertex() in between calls to beginShape() and endShape(). beginShape() can take an argument defining how the vertices should be joined, such as POINTS, LINES or TRIANGLES, but I’ll just use the default (no arguments), which connects vertices in a filled line strip. In other words, the first vertex is connected by a line to the second vertex, which is connected by another line to the third vertex, and so on ad finitum. To generate an unfilled line strip, simply call noFill() somewhere in your code before beginShape(). endShape() can be passed the CLOSE argument, which closes the loop of vertices you have created to make a complete shape.

That should be all you need to start experimenting with the vertex() function. Things get more interesting though if you’re willing to dabble in a little trigonometry. Trigonometry is essential for drawing polygonal shapes with the vertex() function, and you don’t have to be a master to use it to draw some simple shapes. As you’ll probably soon see, my grasp of trigonometry is sketchy at best, but I’ve managed to use it to draw my sample program, which I’ll now talk you through.