Saturday 16 May 2009

Chunk 38 - Final Version

Just to keep my earlier promise, the final version of Chunk 38 is posted below.

CHUNK 38

Introduction


The previous chapter introduced the functions that are available in Processing for depicting lines, as well as explaining the programming constructs of loops and arrays which are widely used in almost all non-trivial programs for code efficiency and elegance. In this chapter we are going to continue to explore the topic of "Drawing Lines" and write a simple program that demonstrates the use of Processing's "Line" functions, with the aid of a few loops and some arrays. Additionally, to maintain the element of spontaneity that characterises all good graphics design, we will use the “Random” function provided by Processing.

Functions in Processing for Drawing Lines

The set of functions in Processing, that might be considered as being designed primarily for drawing lines, consists of the following:

(a) the “line( )” function, which creates a line by joining two points
(b) the “point( )” function, which crates a line by plotting a series of points
(c) the "image( )" function, which generates a line by plotting a series of pixels
(d) the "vertex( )" function, which produces shapes from interconnected lines, each of which connects two vertices (or points).

Additionally, Processing provides functions that are concerned with line attributes such as thickness and colour. These are :

(a) the “strokeWeight( )” function that controls the thickness of lines
(b) the "stroke( ) " function which determines the colour of line
(c) the "strokeCap( )" functions which determines the shape of line end-caps (in JAVA2D mode)

Functions Used for Drawing Lines in this Chapter

It is not the intention in this chapter to explore all of the above functions for the purposes of the program that we are going to construct for drawing lines. The sub-set of the above functions that we shall use consists of the following:

(a) the “line( )” function
(b) the "vertex( )" function
(c) the “strokeWeight( )” function
(d) the "stroke( )" function

Our program will also include Processing's iteration construct of the "For" loop, the curve function of "ellipse( )", the "random( )" function and some arrays.

The line-drawing functions, “line( )”, “strokeWeight( )”, "stroke( )" and "vertex( )", are briefly explained below.

The "line ( )" Function

The "Line ( )" function in Processing has the following format:

line (x1, y1, x2, y2)

where x1, y1 are the X and Y co-ordinates of the start point of the line
and x2, y2 are the X and Y co-ordinates of the end point of he line.

The co-ordinates associated with the start and end points above are used in the same way as Cartesian X and Y co-ordinates commonly encountered in mathematical expressions. However, it should be noted that in Processing the origin (the point where x=0 and y=0), is located at the top left hand corner of the graphics display window, with the positive y-axis being formed by the window's left edge (see figure 1 below).


The "strokeWeight( )" Function

The "strokeWeight( )" functions can be used to set the thickness of a line. The syntax of the function is as follows:

strokeWeight (width), where the width can be an integer or a float. The "width" value sets the thickness of the line in units of pixels.

The "stroke( )" Function

The "stroke( )" function sets the colour that is used to draw a line. The syntax of the "stroke( )" function has seven variants. The two that are used in this chapter are:

(1) stroke (grey), - where "grey " is an integer or a float between 0 and 255 that specifies a shade between white and black. Note that "stroke(0)" is black while "stroke(255)" is white.

(2) stroke (value1, value2, valu3), - where value1 is an integer or a float between 0 and 255 that denotes a shade of the colour red. Likewise values2 and 3 are integers or floats between 0 and 255 that denote shades of green and blue respectively. In each case 255 is the deepest shade of the colour, while 0 indicates its absence.



The "vertex( )" Function

The "vertex( )" function identifies a point within the display window by its X and Y co-ordinates. When enclosed within a pair of "beginShape( )" and "endShape( )" functions, points declared by a "vertex( )" function are connected together with lines to produce a variety of different shapes. The "beginShape( )" functions can have a number of different "modes" which determine the type of shape produced but the default "mode" produces the shape of an irregular polygon. The syntax of the "vertex ()" function, in 2D space, is:

vertex (x, y) - where "x" and "y" are the X and Y co-ordinates respectively.

Figure-2 below shows the construction of a square using the "vertex( )" function together with "beginShape( )" and "endShape( )" functions.
Drawing Lines

Having acquainted ourselves with the "line( )" and "vertex( )" functions above we can now proceed to the next stage of putting them together in a program for drawing lines. The program that we aim to construct concerns "oscillations". The program is modelled on the "Ying Yang" program of Ira Greenberg (see Processing pp 188-189), in that, it is concerned with drawing randomly oscillating lines. It has however, been intentionally designed to avoid using the same "points( )" function that Greenberg uses.

"Drawing Lines 3 - Oscillations" - Program Outline

Our program for drawing oscillating lines, which is being labelled "Drawing Lines 3 - Oscillations" to distinguish it from the line drawing programs of earlier chapters, is in outline as follows:

(a) define the size and background shading to be used for the display window
(b) declare and initialise the constants and the variables to be used in the program
(c) declare and initialise the matrices to be used in the program
(d) Use the "line( )" function to draw oscillating lines of random fluctuations in the display window
(e) use the "vertex( )" function to construct lines that form a "maze" structure as background
(f) Use the "line( )" function to draw diagonal lines with random spacing in the display window
(g) use the "ellipse( )" function to draw a series of concentric ellipses to add to the visual impact of the background

"Drawing Lines 3-Oscillations"- Program Code

In Processing code, the "Drawing Lines 3 - Oscillations" program is as follows:

size (600, 600);
background (0);

//oscillations variables horizontal

float vertices = 60;
int totalRows= 10; // need to be <= height)
int rowGap = height/totalRows;
float rowPush = -1;
float rowFlip = 0;
int randPush = 10;

//diagonal lines variables

int cellWidth = width/int(vertices);
int cellHeight = height/int(vertices);
int randHt = 5;
int randWidth = 5;

//maze pattern variables

float x1, y1;
x1= width/2;
y1= height/2;
int spacing=10;

// strokeWt matrix for zig zag lines thickness

float[] strokeWt = new float [totalRows+1];
for (int j=1; j <totalRows+1; j++){
strokeWt[j] = 1+(3*j/10);
}

//randCellHt matrix for diagonal lines bottom right hand corner

float[] randCellHt = new float [int(vertices)+1];
for (int j=1; j <int(vertices)+1; j++){
randCellHt[j] = cellHeight+int(random(randHt));
}

//randCellWidth matrix for diagonal lines top left hand corner

float[] randCellWidth = new float [int(vertices)+1];
for (int j=1; j <int(vertices)+1; j++){
randCellWidth[j] = cellWidth+int(random(randWidth));
}

//oscillating horizontal lines

stroke (0,255,0);
for (int y=rowGap; y<height; y+=rowGap ){
for (int x=1; x< vertices; x+=3){
rowFlip -= rowPush;
if ( x % (1 + int(random(randPush))) == 0){
rowPush*=-1;
}
strokeWeight (strokeWt[y/rowGap]);
if ( (s = strokeWt[y/rowGap]) > 3){
strokeWeight (3);
}
line((width/vertices*x), y, (width/vertices*(x+1)), (y+rowFlip));
line((width/vertices*(x+1)), (y+rowFlip), (width/vertices*(x+2)), y-(2*rowFlip));
line((width/vertices*(x+2)), y-(2*rowFlip), (width/vertices*(x+3)), y);
}
}

// maze pattern lines

stroke (255);
strokeWeight (1);
noFill();

beginShape();
vertex (x1,y1);
vertex ((x1+(spacing*2)), y1);
for (int j=1, k=1; j < (width/(spacing*2)); j++, k++) {
vertex (x1+(spacing*(j+1)), y1+(spacing*k));
vertex (x1-(spacing*j), y1+(spacing*k));
vertex (x1-(spacing*j), y1-(spacing*k));
vertex (x1+(spacing*(j+2)), y1-(spacing*k));
}
endShape();

//Diagonal lines bottom right corner

stroke(150);
strokeWeight (1);
for (int i=cellHeight, k=1; i<height; i+=cellHeight, k++){
line(width-(i+randCellHt[k]), height, width, height-(i+randCellHt[k]));
}

//Diagonal lines top left corner

stroke(150);
strokeWeight (1);
for (int i=cellWidth, k=1; i<width; i+=cellWidth, k++){
line(0, height-(i+randCellWidth[k]), width-(i+randCellWidth[k]), 0);
}

// background ellipse

strokeWeight (1);
stroke (150);
noFill();
for (int x=1; x< vertices; x+=2){
ellipse((width/2), (height/2), width,((height/2)+(x*5)));
}

A detailed explanation of the program code is given below

Display Window Definitions

The first two lines of the program code, i.e.

size (600, 600);
background (0);

set the size of Processing's display window and its background colour which in this case is black. Note that "background (255)" would have had the opposite effect of displaying the background as white. In the "size" statement, the first parameter refers the width of the display window, while the second sets the height.

Definition of Variables Used with Oscillations Display

This section of the code defines and initialises the variables that are used for the display of oscillating lines. The variables are:

float vertices = 60;
int totalRows= 10; // need to be <= height)
int rowGap = height/totalRows;
float rowPush = -1;
float rowFlip = 0;
int randPush = 10;

The variable "vertices" refers to the points along the x-axis, at which the line displayed changes direction to exhibit an oscillating pattern. The "totalRows" variable sets the number of lines that are to be displayed in the window. In this case, the number is set to be ten and as the associated comment indicates, this number cannot be allowed to exceed the display window's height, as declared in the "size" statement. The "rowGap" variable, as the name suggest, refers to the distance between each horizontal line shown in the window. The "rowPush" and the "rowFlip" variables are used, to control the amount by which the line fluctuates in the vertical direction. These two variables are used in conjunction with the "random( )" function, to make the fluctuations more stochastic. The "randPush" variable is used to "seed" the "random( )" function. In this instance, the function will return a random number between zero and ten.

Variables Used with Diagonal Lines Display

The next set of variables in the program code are concerned with displaying diagonal lines. They are:

int cellWidth = width/int(vertices);
int cellHeight = height/int(vertices);
int randHt = 5;
int randWidth = 5;

The "cellWidth" and "cellHeight" variables set the "fixed" distance between the diagonal lines. To add an element of creative spontaneity, the "fixed" distance is augmented by a random factor which is calculated by using "randHt" and "randWidth" variables as "seeds" for the "random( )" function.

Maze Pattern Variables

In this part of the program, the variables associated with the display of a maze pattern are declared and initialised. These variables are the following:

float x1, y1;
x1= width/2;
y1= height/2;
int spacing=10;

The variables x1 and y1 form the arguments of the "vertex( )" function that plots individual points within the display window. These points are then joined together as lines by using the "beginShape( )" and "endShape( )" functions. Variables x1 and y1 are initialised to be "width/2" and "height/2" respectively, in order to begin the maze in the centre of the display window.

Declaration and Initialisation of Matrices

This section of the program declares and initialises three matrices which are subsequently referred to in the latter stages of the program. The first of these matrices is the "strokeWt" matrix which is used to control the thickness of the oscillating lines being drawn by the program.

float[] strokeWt = new float [totalRows+1];
for (int j=1; j <totalRows+1; j++){
strokeWt[j] = 1+(3*j/10);
}

The "strokeWt" matrix is declared as a "float" type, as the values that its elements hold are decimal fractions rather than whole numbers. The number of elements in the array is one greater than the total number of rows being displayed. This is to compensate for the indexing convention used with Java arrays, where the first element of the array is element 0 rather than 1.


float[] randCellHt = new float [int(vertices)+1];
for (int j=1; j <int(vertices)+1; j++){
randCellHt[j] = cellHeight+int(random(randHt));
}

float[] randCellWidth = new float [int(vertices)+1];
for (int j=1; j <int(vertices)+1; j++){
randCellWidth[j] = cellWidth+int(random(randWidth));
}

The " randCellHt" and the "randCellWidth" matrices are used with the display of diagonal lines. The matrices are initialised with a set of random values that are later used for randomly varying the distance between adjacent diagonal lines. The "randHt" and "randWidth" are variables that are used as "seeds" for the "random( )" function. Because the "random( )" function returns a "float" value, it is converted to an "integer" value using the "int( )" function.

Oscillations

The code in this part of the program addresses the main task of exploring the "line( )" function to produce graphic effects. The code that produces oscillations is as follows:

stroke (0,255,0);
for (int y=rowGap; y<height; y+=rowGap ){
for (int x=1; x< vertices; x+=3){
rowFlip -= rowPush;
if ( x % (1 + int(random(randPush))) == 0){
rowPush*=-1;
}
strokeWeight (strokeWt[y/rowGap]);
if ( (strokeWt[y/rowGap]) > 3){
strokeWeight (3);
}
line((width/vertices*x), y, (width/vertices*(x+1)), (y+rowFlip));
line((width/vertices*(x+1)), (y+rowFlip), (width/vertices*(x+2)), y-(2*rowFlip));
line((width/vertices*(x+2)), y-(2*rowFlip), (width/vertices*(x+3)), y);
}
}

The "stroke( )" function sets the colour of the oscillating lines to be green. The main loop declared by the "for" statement, "for (int y=rowGap; y<height; y+=rowGap )", pushes each new line of oscillations down the y-axis by the pre-set value for " rowGap". Thus, if the value of the "rowGap" variable (which is declared as " int rowGap = height/totalRows") is 60, then each line of oscillations in the display window will be 60 points apart.

The next nested loop in the above code, "for (int x=1; x< vertices; x+=3)", propels the line being drawn along the x-axis, and causes it to oscillate in a vertical direction with each increment of the variable "x".

The " rowFlip" and " rowPush" variables together control the degree of oscillation that the line exhibits. The "if" statement, "if ( x % (1 + int(random(randPush))) == 0)", adds a further degree of randomness to the oscillation by using the "modulo" operator (i.e. "%") together with the "random ( )" function. The "modulo" operator takes two operands, divides the first by the second, and returns the remainder as the resultant value. In this instance, because the second operand consists of a random value, there is a possibility that it may turn out to be zero, in which case it would cause an error, as division by zero is not permissible. To avoid this eventuality, the second operand always has 1 added to the value returned by the "random( )" function (hence, "(1 + int(random(randPush))"). Finally, the "if" statement checks whether the result of the modulo operation is zero, and if so sets the "rowPush" variable to a default negative value by multiplying it with minus 1.

The next three lines are concerned with setting the strokeWeight for depicting the line. The strokeWeight function takes as its argument an element of the "strokeWt" matrix, as indexed by the value of "y", the counter for the main loop. Thus there is a different strokeWeight for every oscillating line. The check to see that the strokeWeight does not exceed 3, has been included merely to avoid the use of too heavy a strokeWeight, which tends to distort unduly the shape of the oscillation.

The final three lines in the above code use the "line( )" function to depict an oscillating line across the display window.

Maze Pattern Lines

stroke (255);
strokeWeight (1);
noFill();

beginShape();
vertex (x1,y1);
vertex ((x1+(spacing*2)), y1);
for (int j=1, k=1; j < (width/(spacing*2)); j++, k++) {
vertex (x1+(spacing*(j+1)), y1+(spacing*k));
vertex (x1-(spacing*j), y1+(spacing*k));
vertex (x1-(spacing*j), y1-(spacing*k));
vertex (x1+(spacing*(j+2)), y1-(spacing*k));
}
endShape();

This section of the code uses the "begiShape( )" and endShape( )" functions together with the "vertex ( )" function, to draw a maze pattern. The variables x1, y1 and "spacing" have been defined in the "variables" section of the program. The correct operation of the loop requires that that the initial line in the maze is declared separately as a single line. Hence the first two "vertex( )" declarations after "beginShape( )".

Drawing Diagonal Lines

In this part of the program, the "line( )" function is used to draw diagonal lines across the display window.

//Diagonal lines bottom right corner

stroke(150);
strokeWeight (1);
for (int i=cellHeight, k=1; i<height; i+=cellHeight, k++){
line(width-(i+randCellHt[k]), height, width, height-(i+randCellHt[k]));
}

//Diagonal lines top left corner

stroke(150);
strokeWeight (1);
for (int i=cellWidth, k=1; i<width; i+=cellWidth, k++){
line(0, height-(i+randCellWidth[k]), width-(i+randCellWidth[k]), 0);
}

For the purposes of drawing diagonal lines, the display window is regarded as being bisected into two adjacent triangles. The first loop in the above code "for (int i=cellHeight, k=1; i<height; i+=cellHeight, k++)" draws diagonal lines in the right hand triangle (which has its apex at point 600,600) , while the second loop, "for (int i=cellWidth, k=1; i<width; i+=cellWidth, k++), carries out the same function for the left hand triangle (which has its apex at point 0,0). The spacing between the lines consists of two elements, - a fixed element that is set by the "cellWidth" and "cellHeight" variables and a random element that is fetched from the "randCellHt" and "randCellWidth" matrices, both of which are initialised earlier in the program. The loop uses two variables as counters rather than the usual one. The variable "i" is used to assign the fixed part of the spacing and the variable "k" to assign the random element.

Concentric Ellipses

The code contained in the final section of the program draws a series of concentric ellipses whose overall effect is to enhance the visual impact of the background.

strokeWeight (1);
stroke (150);
noFill();
for (int x=1; x< vertices; x+=2){
ellipse((width/2), (height/2), width,((height/2)+(x*5)));
}

The "noFill( )" statement prevents the space within the concentric ellipses from being filled with white space. Without the "noFill( )" statement, the concentric ellipses would appear as a blur. The four arguments to the "ellipse( )" function are the X co-ordinate of the ellipse's origin, the Y coordinate of the origin, the width and the height of the ellipse, respectively. The origin of the ellipse is at the centre of the display window, its width is the width of window with the height initially set at half the height of the display window.

The output from the "Oscillations" program is displayed at Figure-3 and Figure-4 below.






Amendments to the "Oscillations" Program - "Bi-directional Oscillations"

The "Oscillations" program above may be amended to produce "Bi-directional Oscillations" by adding the code shown below.

//Amendments for Bi-directional Amendments

//oscillations variables vertical
int totalCols= 10; // need to be <= 300 (or height)
int colGap = width/totalCols;
float colPush = -1;
float colFlip = 0;
stroke (0,255,0);
strokeWeight(2);

//oscillations vertical
for (int x=colGap; x<width; x+=colGap ){
for (int y=1; y< vertices; y+=3){
colFlip -= colPush;
if ( y % (1 + (int) (random(randPush))) == 0){
colPush*=-1;
}
line( x, (height/vertices*y), (x+colFlip), (height/vertices*(y+1)));
line((x+colFlip), (height/vertices*(y+1)), x-(2*colFlip), (height/vertices*(y+2)) );
line(x-(2*colFlip), (height/vertices*(y+2)), x, (height/vertices*(y+3)));
}
}

The above code works in the same fashion as that at explained at "Oscillatons" above, except that it operates on columns instead of rows. The amended program in full is as follows:


size (600, 600);
background (0);

//zig zag variables horizontal

float vertices = 60;
int totalRows= 10; // need to be <= height)
int rowGap = height/totalRows;
float rowPush = -1;
float rowFlip = 0;
int randPush = 10;

//diagonal lines variables

int cellWidth = width/int(vertices);
int cellHeight = height/int(vertices);
int randHt = 5;
int randWidth = 5;

//maze pattern variables

float x1, y1;
x1= width/2;
y1= height/2;
int spacing=10;

// strokeWt matrix for zig zag lines thickness

float[] strokeWt = new float [totalRows+1];
for (int j=1; j <totalRows+1; j++){
strokeWt[j] = 1+(3*j/10);
}

//randCellHt matrix for diagonal lines bottom right hand corner

float[] randCellHt = new float [int(vertices)+1];
for (int j=1; j <int(vertices)+1; j++){
randCellHt[j] = cellHeight+int(random(randHt));
}

//randCellWidth matrix for diagonal lines top left hand corner

float[] randCellWidth = new float [int(vertices)+1];
for (int j=1; j <int(vertices)+1; j++){
randCellWidth[j] = cellWidth+int(random(randWidth));
}

//zig zag pattern horizontal lines

stroke (0,255,0);
for (int y=rowGap; y<height; y+=rowGap ){
for (int x=1; x< vertices; x+=3){
rowFlip -= rowPush;
if ( x % (1 + int(random(randPush))) == 0){
rowPush*=-1;
}
strokeWeight (strokeWt[y/rowGap]);
if ((strokeWt[y/rowGap]) > 3){
strokeWeight (3);
}
line((width/vertices*x), y, (width/vertices*(x+1)), (y+rowFlip));
line((width/vertices*(x+1)), (y+rowFlip), (width/vertices*(x+2)), y-(2*rowFlip));
line((width/vertices*(x+2)), y-(2*rowFlip), (width/vertices*(x+3)), y);
}
}

// maze pattern lines

stroke (255);
strokeWeight (1);
noFill();

beginShape();
vertex (x1,y1);
vertex ((x1+(spacing*2)), y1);
for (int j=1, k=1; j < (width/(spacing*2)); j++, k++) {
vertex (x1+(spacing*(j+1)), y1+(spacing*k));
vertex (x1-(spacing*j), y1+(spacing*k));
vertex (x1-(spacing*j), y1-(spacing*k));
vertex (x1+(spacing*(j+2)), y1-(spacing*k));
}
endShape();

//Diagonal lines bottom right corner

stroke(150);
strokeWeight (1);
for (int i=cellHeight, k=1; i<height; i+=cellHeight, k++){
line(width-(i+randCellHt[k]), height, width, height-(i+randCellHt[k]));
}

//Diagonal lines top left corner

stroke(150);
strokeWeight (1);
for (int i=cellWidth, k=1; i<width; i+=cellWidth, k++){
line(0, height-(i+randCellWidth[k]), width-(i+randCellWidth[k]), 0);
}

// background ellipse

strokeWeight (1);
stroke (150);
noFill();
for (int x=1; x< vertices; x+=2){
ellipse((width/2), (height/2), width,((height/2)+(x*5)));
}

//Amendments for Bi-directional Amendments

//oscillations variables vertical
int totalCols= 10; // need to be <= 300 (or height)
int colGap = width/totalCols;
float colPush = -1;
float colFlip = 0;
stroke (0,255,0);
strokeWeight(2);

//oscillations vertical
for (int x=colGap; x<width; x+=colGap ){
for (int y=1; y< vertices; y+=3){
colFlip -= colPush;
if ( y % (1 + (int) (random(randPush))) == 0){
colPush*=-1;
}
line( x, (height/vertices*y), (x+colFlip), (height/vertices*(y+1)));
line((x+colFlip), (height/vertices*(y+1)), x-(2*colFlip), (height/vertices*(y+2)) );
line(x-(2*colFlip), (height/vertices*(y+2)), x, (height/vertices*(y+3)));
}
}

The output form the amended "Bi-directional Oscillations" program is shown at Figure -5 below

Wednesday 13 May 2009

Monday 11 May 2009

Chunk 38

Managed to complete Chunk 38, although a couple of days late. On the whole, I have enjoyed the experience. I am hoping, of course, that I have got my two chunks about right. I'll attach the final versions of both Chunks to-morrow, - after I have worked the best way to upload the graphic output.

Tuesday 5 May 2009

Chunk38 - Update

Still in the process of completing the text for Chunk38, but hopefull that I'll be able to meet the deadline (7/5/09). The start on Chunk 38 was a bit confused for me. Darrel had nominated me via the "Book Fragments" blog, but I had rather thought that he would nominate me by e-mail. Just as well that I checked his blog. Anyway, the chunk has made good progress so far, and should be ready in time.