In this chunk we will be looking at how we can use vertex functions to create 2-dimensional shapes and animate them to create a fascinating complex-looking animation made from many simple shapes. We will see how we can create lots of 2D hexagons moving in 3D space – a hexagon wave.
We saw in chunk 44 how a shape can be created using lines and vertices, so we will begin by looking at how we can create a hexagon by this method. To do this, we need to work out how to draw each line that makes up the edges of the hexagon shape. This can be done using trigonometry as shown in figure 47.1 below. Trigonometric functions are covered later, so for now you will have to trust is that this works.
Figure 47.1 The lines needed to draw a hexagon shape
The corresponding code for drawing the hexagon looks like this
// variables for the hexagon sides
float c = 200;
float a = c/2;
float b = sin(radians(60)) * c;
// draw hex shape
beginShape();
vertex(0, b);
vertex(a, 0);
vertex(a + c, 0);
vertex(2 * c, b);
vertex(a + c, 2 * b);
vertex(a + c, 2 * b);
vertex(a, 2 * b);
vertex(0, b);
endShape();
This piece of code will create a shape containing the vertices we define – a line will be drawn between each vertex creating the shape defined between beginShape() and endShape(). If you run this, you will see that a hexagon is drawn similar to that shown in figure 47.2
Figure 47.2 Hexagon shape drawn using vertices
We can make the hexagon slightly more interesting by drawing some lines from each vertex to the centre of the shape. Again, you will have to trust us on the coordinates, but the centre point of the hexagon can be found at point (2*a, b). We already know the points for each vertex as we used them above, so the additional code looks like:
// draw a line from each vertex to the centre of the hexagon
line(0, b, 2 * a, b);
line(a, 0, 2 * a, b);
line(a + c, 0, 2 * a, b);
line(2 * c, b, 2 * a, b);
line(a + c, 2 * b, 2 * a, b);
line(a + c, 2 * b, 2 * a, b);
line(a, 2 * b, 2 * a, b);
Running this along with the code for drawing the hexagon creates a shape like that shown in figure 47.3
Figure 47.3 Hexagon shape with lines to the centre
Figure 47.3 Hexagon shape with lines to the centre
Now that we have our hexagon, we can start to think about animating it. Processing has rotation functions that allow objects drawn on the screen to be rotated around the x-, y- or z-axis, for example
rotateY(0.1);
will rotate anything drawn after the rotate function by the amount given. In order to do this, either P3D or OPENGL mode needs to be enabled. We will use P3D as it doesn't rely on having an OpenGL-compatible graphics card. This is enabled by specifying it in the size() function eg
size(800, 600, P3D);
However, this will only rotate the hexagon once, where it will appear to be static. To animate a rotating hexagon, we need to use Processing's continuous mode (as seen in chunk 30) and set the background colour each time so the screen is redrawn. We also need to change the rotation each time it is drawn. This is necessary because the rotation is reset each time the draw() function is run so, to give the illusion of rotation, we need to increase the rotation each time the hexagon is drawn. The program so far looks like:
float rotation = 0.1;
void setup() {
// 3D mode
size(900, 500, P3D);
}
void draw() {
background(0);
rotateY(rotation);
// variables for the hexagon sides
float c = 200;
float a = c/2;
float b = sin(radians(60)) * c;
// draw hex shape
beginShape();
vertex(0, b);
vertex(a, 0);
vertex(a + c, 0);
vertex(2 * c, b);
vertex(a + c, 2 * b);
vertex(a + c, 2 * b);
vertex(a, 2 * b);
vertex(0, b);
endShape();
// draw a line from each vertex to the centre of the hexagon
line(0, b, 2 * a, b);
line(a, 0, 2 * a, b);
line(a + c, 0, 2 * a, b);
line(2 * c, b, 2 * a, b);
line(a + c, 2 * b, 2 * a, b);
line(a + c, 2 * b, 2 * a, b);
line(a, 2 * b, 2 * a, b);
rotation += 0.01;
}
Running this will produce a rotating hexagon animation. However, you will see that it rotates from its leftmost point, rather than rotating around the centre of the hexagon. This is because the rotation is taken from the 'origin' of the shape, in this case point (0,b) because we have drawn the shape in relation to that point. To rotate around the centre (which we already have worked out as 2 *a, b) we need to offset the vertices and lines by this point. We will do this by defining points x and y the negative of the centre point ie
float x = -(2 * a);
float y = -b;
Now if x and y are added to the previous vertices coordinates, the hexagon will be drawn in relation to the centre point, giving us
float rotation = 0.1;
void setup() {
// 3D mode
size(900, 500, P3D);
}
void draw() {
// draw in the middle of the screen
translate(width/2,height/2); // 1
// reset the page with a background colour
background(0);
// rotate around the Y axis
rotateY(rotation);
// variables for the hexagon sides
float c = 200;
float a = c/2;
float b = sin(radians(60)) * c;
// define the centre variables
float x = -(2 * a);
float y = -b;
// draw hex shape
beginShape();
vertex(x, y + b);
vertex(x + a, y);
vertex(x + a + c, y);
vertex(x + (2 * c), y + b);
vertex(x + a + c, y + (2 * b));
vertex(x + a + c, y + (2 * b));
vertex(x + a, y + (2 * b));
vertex(x, y + b);
endShape();
// draw a line from each vertex to the centre of the hexagon
line(x, y + b, x + (2 * a), y + b);
line(x + a, y, x + (2 * a), y + b);
line(x + a + c, y, x + (2 * a), y + b);
line(x + (2 * c), y + b, x + (2 * a), y + b);
line(x + a + c, y + (2 * b), x + (2 * a), y + b);
line(x + a + c, y + (2 * b), x + (2 * a), y + b);
line(x + a, y + (2 * b), x + (2 * a), y + b);
// increase rotation
rotation += 0.01;
}
Running this will now display our hexagon rotating around its centre point – a nice little animation, although not particularly exciting. We needed to add an extra line of code here to display the hexagon properly, in the middle of the screen (labelled //1 in the code above). The translate function in Processing moves the origin of the graphics following the translate() to the position given in the function. In our case we are telling Processing to draw the hexagon and lines in the centre of the screen, shown by (width/2,height/2)
Figure 47.4 Centred rotating hexagon
To make this more exciting, we will now replicate the hexagon to produce lots of hexagons. This could be done using arrays of vertex points representing the vertices of each hexagon, but this would get complicated very quickly. Fortunately, Processing has a better way of reproducing graphics that we can use to just keep drawing the same hexagon but display many versions of it – the matrix stack.
We won't explain exactly how the matrix stack works, except to say that it rests behind the way Processing works in order to display graphics using a stack (like a stack of coins, you can only get to the top coin so have to work down the stack of coins to get to the bottom). By getting access to the matrix (using pushMatrix()), we can alter the way graphics are displayed, then put the matrix back again (using popMatrix()) for Processing to display. If we were to alter the graphics by displaying a hexagon and moving it using the translate() function, many times, we would give the appearance of lots of hexagons without needing to maintain where each one should be displayed – this can be a very powerful feature, which we will now demonstrate.
float rotation = 0.1;
// the number of hexagons
int xCount = 10; //1
// the length of 1 hexagon side
int sideLength = 50; //2
void setup() {
// 3D mode
size(900, 500, P3D);
}
void draw() {
// reset the page with a background colour
background(0);
// loop the number of times specified
for (int i = 0; i < xCount; i ++) { //3
// push the matrix
pushMatrix(); //4
// translate the hexagon to one sise
translate((sideLength * 2) * i, height/2); //5
// rotate around the Y axis
rotateY(rotation);
// variables for the hexagon sides
float c = sideLength;
float a = c/2;
float b = sin(radians(60)) * c;
// define the centre variables
float x = -(2 * a);
float y = -b;
// draw hex shape
beginShape();
vertex(x, y + b);
vertex(x + a, y);
vertex(x + a + c, y);
vertex(x + (2 * c), y + b);
vertex(x + a + c, y + (2 * b));
vertex(x + a + c, y + (2 * b));
vertex(x + a, y + (2 * b));
vertex(x, y + b);
endShape();
// draw a line from each vertex to the centre of the hexagon
line(x, y + b, x + (2 * a), y + b);
line(x + a, y, x + (2 * a), y + b);
line(x + a + c, y, x + (2 * a), y + b);
line(x + (2 * c), y + b, x + (2 * a), y + b);
line(x + a + c, y + (2 * b), x + (2 * a), y + b);
line(x + a + c, y + (2 * b), x + (2 * a), y + b);
line(x + a, y + (2 * b), x + (2 * a), y + b);
popMatrix(); //6
}
// increase rotation
rotation += 0.01;
}
If you run the above code, you will see that we have created ten hexagons without storing where each one is being displayed at all! Each one is just a copy of the same hexagon, displayed in a different place and rotated.
Figure 47.5 Ten hexagons created using the translate() function
The changes we made to do this are very small, using just 6 lines of additional code (numbered above). In lines //1 and //2 we have created variables to control the number of hexagons and the size of each side of the hexagon. We use a for statement in line //3 to loop the number of times defined in the xCount variable, creating a hexagon each time, translating each one (line //5) by shifting each to the side. Lines //4 and //6 are the matrix methods that enable this to work. It would be easy, using this method, to fill the screen with hexagons by translating more of them using a further for statement, for example
for (int i = 0; i <= xCount; i++) {
// translate each hexagon to the side
translate(10 * i, 0, 0);
// for every column of hexagons
for (int j = 0; j <= yCount; j++) {
translate((sideLength * 2), (sideLength * 2), 0);
In order to tidy up our hexagon drawing code and prevent duplication (you will see why later), we can also move this into a function so that we can call the function each time we want to draw one. We have called this function drawHexagon() and created a function for drawing the lines called drawLines(),
We can now start to make our animation really exciting by making the hexagons more wave-like. If we define a maximum and minimum number of hexagons, we can increase and decrease them with each run of the draw() function, like a tide. We will need several variables for this
// maximum number of hexagons on the Y and X axes
int maxXCount = 20;
int maxYCount = 20;
// minimum number of hexagons on the Y anc X axis
int minXCount = 1;
int minYCount = 1;
// current number of hexagons
int xCount = minXCount;
int yCount = minYCount;
// whether the number of hexagons is increasing
boolean xIncreasing = true;
boolean yIncreasing = true;
Each time the draw() function is run, we will either increase or decrease the number of hexagons depending on whether the current number of hexagons is less than the minimum number or greater than the maximum. These will be controlled using some functions that will determine whether they should be increasing or not and altering the current count (xCount and yCount) accordingly.
/**
* determine the direction of the hexagons
*/
void determineDirection() {
// if the x-axis is increasing
if (xIncreasing) {
// increase the number of hexagons
xCount++;
}
// otherwise decrese them
else {
xCount--;
}
// if the y-axis is increasing
if (yIncreasing) {
// increase the number of hexagons
yCount++;
}
// otherwise decrease them
else {
yCount--;
}
}
/**
*Determine whether the number of hexagons should be increasing or not
*/
void determineIncreasingStatus() {
// if the number of hexagons on the x-axis is greater than the maximum
if (xCount > maxXCount) {
// set to decrease
xIncreasing = false;
}
// if the number of hexagons on the x-axis is greater than the maximum
if (yCount > maxYCount) {
// set to decrease
yIncreasing = false;
}
// if the number of hexagons on the x-axis is less than the minimum
if (xCount < minXCount) {
// set to increase
xIncreasing = true;
}
// if the number of hexagons on the y-axis is less than the minimum
if (yCount < minYCount) {
// set to increase
yIncreasing = true;
}
}
If we call these functions from the draw() function, we will have created our wave effect – the hexagons will increase until they reach the maximum, then decrease again. Our draw() function now looks like:
void draw() {
// determine how many hexagons there should be
determineDirection();
// reset the page with a background colour
background(0);
// loop for the number of hexagons on the x-axis
for (int i = 0; i <= xCount; i++) {
// push the matrix
pushMatrix();
// translate each hexagon to the side
translate((sideLength * 2) * i, 0, 0);
// loop for the number of hexagons on the y-axis
for (int j = 0; j <= yCount; j++) {
translate((sideLength * 2), (sideLength * 2), 0);
// rotate around the Y axis
rotateY(rotation);
// draw the hexagon
drawHexagon();
// draw the lines
drawLines();
}
popMatrix();
}
// increase rotation
rotation += 0.01;
// determine if the hexagons should be increasing in number
determineIncreasingStatus();
}
Running our code now will produce an increasing, then decreasing, number of hexagons, all rotating at slightly different points, as shown in figure 47.6
Figure 47.6 Wave of hexagons
You will notice, however, that is rather fast and not altogether wave like! We can sort this out by slowing down the animation using Processing's frameRate() function. This controls how fast the animation is displayed – the default is 30, but 8 seems to produce the best effect for this animation. Adding the following into the setUp() function should produce the desired result
frameRate(8);
Now all that remains is make our animation more fun! We can make a really good wave effect by translating the whole screen to skew the viewing angle, increasing the number of hexagons and reducing their size. All we need to do is increase the maxXCount and maxYCount variables to 80, decrease the sideLength variable to 5 and add the following line to the to the top of the draw() function
rotateX(1.0);
This line will rotate the entire viewing angle of the screen. Running it now will display an interesting wave-like effect – the hexagons will seem to 'wash' in from the left of the screen in a wave formation, then wash out again.
Figure 47.7 Hexagon wave
That is essentially the core of our line-based animation. We have created an animation of rotating hexagons that exhibit a wave-like formation by rotating the screen and drawing a differing number of hexagons each time the draw() function is run. Different animations can be obtained by altering the program a little, for example adding colour (explored later in the book) or creating multiple waves. This is where moving the hexagon-drawing code into a function can prove useful. We have created two waves by moving some of the draw() code into a drawLeftWave() which is called from the draw() function so that we can also create a drawRightWave() function that starts from the top-right corner by reversing the x-axis attribute in the translate() function. The code for this is below – try it and watch what happens when the two waves meet each other. You can alter it however you want with just minor changes and watch the effects
// the rotation of the hexagons
float rotation;
// maximum number of hexagons on the Y and X axes
int maxXCount;
int maxYCount;
// minimum number of hexagons on the Y anc X axis
int minXCount;
int minYCount;
// current number of hexagons
int xCount;
int yCount;
// whether the number of hexagons are increasing
boolean xIncreasing;
boolean yIncreasing;
// variables for the hexagon sides
float a;
float b;
float c;
// variables for the centre of a hexagon
float x;
float y;
// the length of a single hexagon side
int sideLength = 5;
/**
* setup and initialise the sketch
*/
void setup() {
// 3D mode
size(900, 500, P3D);
// calculate the sides of the hexagon
c = sideLength;
a = c / 2;
b = sin(radians(60)) * c;
// origin coordinates for the hexagon
x = -(2 * a);
y = -b;
// initialise the maximum number of hexagons
maxXCount = 80;
maxYCount = 80;
// initialise the minimum number of hexagons
minXCount = 1;
minYCount = 1;
// initialise the starting number of hexagons as the minimum number
xCount = minXCount;
yCount = minYCount;
// start by increasing the number of hexagons
xIncreasing = true;
yIncreasing = true;
// start by rotating anti-clockwise
rotation = -PI;
// make the frame rate quite low
frameRate(8);
}
/**
* Draw the screen in repeat mode
*/
void draw() {
// determine how many hexagons there should be
determineDirection();
// reset the page with a background colour
background(0);
// rotate the entire display
rotateX(1.0);
// draw the left wave of hxagons
drawLeftWave();
// draw the right wave of hexagons
drawRightWave();
// increase rotation
rotation += 0.01;
// determine whether the hexagons should be increasing or decreasing
determineIncreasingStatus();
}
/**
* Draw the left wave of hexagons
*/
void drawLeftWave() {
// fill the hexagons with yellow and alpha value
fill(238, 238, 0, 150);
// for row of hexagons
for (int i = 0; i <= xCount; i++) {
// push the matrix
pushMatrix();
// translate each hexagon to the side
translate((sideLength * 2) * i, 0, 0);
// for every column of hexagons
for (int j = 0; j <= yCount; j++) {
// translate each forward and sideways
translate((sideLength * 2), (sideLength * 2), 0);
// rotate around the y axis
rotateY(rotation);
// draw a hexagon
drawHexagon();
// draw the interior lines
drawLines();
}
popMatrix();
}
}
/**
* Draw the right wave of hexagons
*/
void drawRightWave() {
// fill the hexagons with red and alpha value
fill(165, 42, 42, 150);
// for row of hexagons
for (int i = 0; i <= xCount; i++) {
// push the matrix
pushMatrix();
// translate each hexagon to the side
translate(width - (sideLength * 2) * i, 0, 0);
// for every column of hexagons
for (int j = 0; j <= yCount; j++) {
// translate each forward and sideways
translate((sideLength * 2), (sideLength * 2), 0);
// rotate around the y axis
rotateY(rotation);
// draw a hexagon
drawHexagon();
// draw the interior lines
drawLines();
}
popMatrix();
}
}
/**
* determine the direction of the hexagons
*/
void determineDirection() {
// if the x-axis is increasing
if (xIncreasing) {
// increase the number of hexagons
xCount++;
}
// otherwise decrese them
else {
xCount--;
}
// if the y-axis is increasing
if (yIncreasing) {
// increase the number of hexagons
yCount++;
}
// otherwise decrese them
else {
yCount--;
}
}
/**
*Determine whether the number of hexagons should be increasing or not
*/
void determineIncreasingStatus() {
// if the number of hexagons on the x-axis is greater than the maximum
if (xCount > maxXCount) {
// set to decrease
xIncreasing = false;
}
// if the number of hexagons on the x-axis is greater than the maximum
if (yCount > maxYCount) {
// set to decrease
yIncreasing = false;
}
// if the number of hexagons on the x-axis is less than the minimum
if (xCount < minXCount) {
// set to increase
xIncreasing = true;
}
// if the number of hexagons on the y-axis is less than the minimum
if (yCount < minYCount) {
// set to increase
yIncreasing = true;
}
}
/**
* Draw a hexagon
*/
void drawHexagon() {
// draw hex shape
beginShape();
vertex(x, y + b);
vertex(x + a, y);
vertex(x + a + c, y);
vertex(x + (2 * c), y + b);
vertex(x + a + c, y + (2 * b));
vertex(x + a + c, y + (2 * b));
vertex(x + a, y + (2 * b));
vertex(x, y + b);
endShape();
}
/**
* Draw the interior lines of the hexagon
*/
void drawLines() {
// draw a line from each vertex to the centre of the hexago
line(x, y + b, x + (2 * a), y + b);
line(x + a, y, x + (2 * a), y + b);
line(x + a + c, y, x + (2 * a), y + b);
line(x + (2 * c), y + b, x + (2 * a), y + b);
line(x + a + c, y + (2 * b), x + (2 * a), y + b);
line(x + a + c, y + (2 * b), x + (2 * a), y + b);
line(x + a, y + (2 * b), x + (2 * a), y + b);
}
Figure 47.8 Hexagon waves
Figure 47.9 Hexagons waves crashing into each other