Monday 27 April 2009

Chunk 25
(pp 124-131 , Greenberg)

Geometry and Computer Art – Polynomial Curves


In the last section we saw how to draw simple straight lines. Now we look at how to draw two simple polynomial curves using those straight lines.
Straight Line
A straight line is represented by the general formula:
y =mx + c
Quadratic Curve
A quadratic, or 'first degree' curve is represented by the general formula :
y = nx2 + mx + c
Cubic Curve
A cubic or 'second degree' curve is represented by the general formula:
y = kx3 + nx2 + mx + c

Draw the Curves with Straight Lines
As you can see, the difference between the straight and the new (curved) lines is the addition of the increment of x changing by one or more powers of x as well as plain x. (If you think of plain x as being x1 and a straight line being 'zeroth degree' you can see the progression).
So how do we use the formulae to draw curves? Well we don't, exactly. What we draw is a series of short straight lines just as in the last section. Each line starts where the previous line ends, and each is set at a slight angle to the previous line.
To do this we use a loop to change the values of x, and for each value of x we draw a short line. Try out the program quadratic_lines_colours. This short program draws a quadratic curve by looping through a series of values of x, gradually building up a parabolic curve a short line at a time. The lines have been alternately coloured yellow and blue to make it more obvious.


Now try running cubic_lines_colours which draws a cubic (second degree curve, also made up of lots of blue and yellow lines).



Although it might not appear so from the pictures, all those lines actually are straight, even though the overall impression is of a curve. You can make the straightness of the lines more pronounced by changing the values of 'step' and/or 'maxi' in the code of either program. These values represent the actual number of lines that will be drawn. The 'maxi' value is used to describe the size of the x value used in the formula, and the 'step' value is used to decide how many of the values in the loop range will be drawn.
So, for instance, in the quadratic program, try changing the 'maxi' value to 200 and the step to 50. This will make the quadratic curve be drawn using just a few lines, and make the straightness of the lines much more obvious. Try a similar thing with the cubic curve. Set the two values so that maxi/step gives about 8 or 10.
Turning Points
As you may have noticed, the curves we have drawn don't just curve, they turn too. The places where they turn (turning points) depend upon the values for the constants fed into the formulae. However, in general, we can predict that a quadratic (1st degree) curve will have one turning point, and a cubic (2nd degree) curve will have two. The amount of the turn at a turning point depends, again, on what figures are fed into the formula (and loop). For instance, with the cubic curve, try setting the value 'c' to 5. What happens to the curve? Instead of there being a fairly flat line between the two turning points, now we have a veritable hill between them. You won't get a visible change in the quadratic program because of the way the program values have been squashed to fit in the window, but that sort of curve can also be made flatter or more pointed.

Overview of the Programs
Both the quadratic and cubic programs follow the same pattern, with just a few differences between them. There are three main sections to the programs.
Main Initialisation
Here we declare the basic values that will be used in the porgram. First variables are declared to hold the values of the start and end points of the lines we draw. Then we set the dimensions of the window. You can make these dimensions anything you like. The limit is really just the size of your screen. Whatever size you make the window, its size will be taken into account later when we scale the curves to fit.
The declaration of a boolean (a sort of on/off switch) here, myColour, is because the value this holds needs to be retained between loops, so we have to declare it outside the loop even though it is only used within the loop.
For the scaling calculation we need to know the magnitude of the loop counter maxi, so we declare it here, before that calculation, rather than within the loop mechanism

Quadratic and Cubic
// Initialise
float startX;
float startY;
float endX;
float endY;
size(500,500); // screen area
boolean myColour = true;
int maxi = 50;

Looping
All the variables (except the loop counter) have already been declared. Now it's time to draw all the lines that make up the curve.
Start Values
For each line we draw, the loop needs to give a starting and ending point for that line, with values for both x and y for each end. Apart from the first time through the loop, the starting point for a line doesn't need to be calculated. Because each line immediately follows the previous one, we can simply use the end point of the previous line as the start point of the current line. This is done at the end of the loop after the first time. The first time we need to specifically set the start x and y before entering the loop. These were declared at the top of the program but not given values then. Now we set their initial values.
The start values are slightly different for the two curves. This is so that we don't unintentionally draw a line on the screen while getting to the start of the curve. In the quadratic curve the curve starts being drown off screen under the bottom border of the window. For the cubic curve the start of the curve is above the top border of the window.
Count through the loop
First we set up a counter and apply to it the maxi value we declared and initialised earlier. In order to make the turning points visible, we need to start the looping while x is a negative number. If we used only positive numbers for the looping counter we would get a curve, but only part of one.
Evaluating the formula
Now we come to the nitty-gritty of the program – the formula that decides what sort of curve is to be drawn. This is very straightforward. We simply write out the formula to get y, substituting in the relevant variable values we set up earlier, or created in the loop. Remember we set the values for the constants (the quantities of the x powers) when sorting out the squeezing and shifting. At this stage the squeezing and shifting factors are also applied. Note that these are applied to the whole value of y after the rest of the equation has been evaluated. They are not part of the basic evaluation itself.
In the endY calculation we could have used the pow() function again, and normally would. However to make things clearer we have shown multiplying out of the powers in full in the formula in the loop.
Stepping through the counter
The step value is used together with the loop size (maxi) to control the number and length of lines to be drawn inside the loop. The size of the loop is determined by the minimum andf maximum values of the loop counter, while the step value sets how many of the loop counter values will be used. If the step is 1 then all values will be used. If it's 2 then only every other value will be used. A value of 3 will use every third value of the counter and so on. If you want you can try setting it to a negative value, but you'll also need to change the loop start/end points otherwise no loops will be made.
Colouring the lines
For the line colours we want to use alternate colours so that the change from one line to another in the curve can be seen. For this we use the switch, myColour, that was declared earlier. Each time the program goes through the loop it checks to see what value myColour holds. Whichever one it is (it can only be either true or false) the switch is set to the other position, and the colour to be used for drawing is changed. Note that a line itself cannot be coloured, so instead we colour the stroke which is the outline of the line.
Drawing the lines
The next step is to actually draw the line for this loop using the values we have just calculated for the end point x and y placement. All that's left now is to set up the start points for the new line. Since the next line follows exactly where this one leaves off, just set the new start as this line's ending.
Quadratic Curve
// size of step through loop
int step = 5; // smoothness


// The drawing loop
startX = 0;
startY = height;

for(int i = -maxi; i
// set end of this line (x and y coords)
endY = (c*i*i + b*i + a)*stretchy + offsety;
endX = i*stretchx + offsetx -1;

// change the colour of the this line
if (myColour == false){
myColour = true;
stroke(255,255,0);
}
else{
myColour = false;
stroke(0,0,255);
}
// draw the line
line(startX, startY, endX, endY);

// set start of next line to end of this one
startX = endX;
startY = endY;

}


Cubic Curve

// size of step through loop
int step = 5; // smoothness

// The drawing loop
startX = 0;
startY = 0;

for(int i = -maxi; i
// set end of this line (x and y coords)
endY = (d*i*i*i - c*i*i + b*i + a)*stretchy + offsety;
endX = i*stretchx + offsetx;

// change the colour of the this line
if (myColour == false){
myColour = true;
stroke(255,255,0);
}
else{
myColour = false;
stroke(0,0,255);
}
// draw the line
line(startX, startY, endX, endY);

// set start of next line to end of this one
startX = endX;
startY = endY;

}




Squashing and Shifting
Fitting the curve into the window
With a straight line there is no problem choosing values for the formula so that the line will fit into the window. That doesn't hold for the cubic and quadratic curves because of the powers of numbers that are in the formulae. With a straight line, if you loop through a series of lines in the same way as we are with the curves, each line will be the same length as all the other lines. With curves the lines are all different lengths.
Suppose the loop value (maxi) were set as 3 in a straight line program made the same way as our curve programs. The following tables show a portion of the x,y values for each one of the curves and the straight line, each using the same values of x. As you can see, the y value for the cubic curve is about fifty times bigger than for the straight line, where x is only 10 and the constant values are pretty low. So for the cubic and quadratic curves we need to do a bit of squashing and scaling to make the curve fit our window. Fortunately this is very easy to do (though at first sight it might seem horrendous).









line
quadratic
cubic
x
x*x
x*x*x
j
k
m
c
y
y
y
1
1
1
2
3
4
5
9
12
14
2
4
8
2
3
4
5
13
25
41
3
9
27
2
3
4
5
17
44
98
4
16
64
2
3
4
5
21
69
197
5
25
125
2
3
4
5
25
100
350
6
36
216
2
3
4
5
29
137
569
7
49
343
2
3
4
5
33
180
866
8
64
512
2
3
4
5
37
229
1253
9
81
729
2
3
4
5
41
284
1742
10
100
1000
2
3
4
5
45
345
2345
Table 1: Example values of y in the different formulae, with sample values of x.
Squeezing the Curve
To squeeze the curve we need to know the formula to be used, and the minimum and maximum values for the loop. In each case the formula is evaluated (worked out) substituting the maximum and minimum loop values. This gives us the largest number that y can possibly be with the given constants. This value is then applied to the height of the window to get the scale that needs to be applied to the actual y values that result when the program is run.
The term 'stretchy' is used here, but in fact the value is being squashed not stretched in this example. Whether the scale factor squashes or stretches,doesn't matter for the formulae below.
A function pow() is used below. It's very simple to use. The pattern is pow(number_to_be_multiplied, number_of_the_power). So for instance :
34 = pow(3,4)
62 = pow(6,2)
Quadratic Scaling for y
// make sure the curve fits the window height
int a = 0;
int b = 1;
int c = 1;

float stretchy = height/(c*pow(maxi, 2) + b*maxi + a);
Cubic Scaling for y
// make sure the curve fits the window height
int a = 1;
int b = 1;
int c = 1;
int d = 1;
float stretchy = height/(d*pow(maxi, 3) - c*pow(maxi, 2) + b*maxi + a);

Similarly, depending on the value given for the number of times the loop will be performed may make the curve shoot off the screen horizontally too. To prevent this we scale on the x-axis too, though it's lot simpler this time.
Quadratic Scaling for x
// make sure the curve fits the window width
float stretchx = width/maxi/2;
Cubic Scaling for x
// make sure the curve fits the window width
float stretchx = width/maxi/2;
Offset Values
As well as squashing the curves to make them the right proportions for the window, it may also be necessary to shift the whole curve vertically, horizontally or both so that it is visible in the window. In this case we don't want to multiply by the result of the formula, but add or subtract an offset instead. The following lines show how this is done. Bear in mind that having set the dimensions of the window we can use two values as constants; height and width. These refer to the height and width of the window itself, not the height/width of the curve drawn in the window. In general, to centre something, you use half the height and half the width, remembering that the y dimension (vertical) counts from the top border down, not from the bottom border up.

Quadratic Offsets
// set up offsets
int offsetx = width/2;
int offsety = height/5;
Cubic Offsets
// set up offsets
int offsetx = width/2;
int offsety = height/2;

The part of the curve being offset in each case is the turning point or points. For the quadratic curve we could use height/2, and that would place the turning point in the centre of the window. However, in order to see the turning point in context it's simpler to place the turning point nearer to the top of the window. Play about with the figures and see what they do to place the curves in the position you want them.
A Fancy Variation
Once we have these basic curves they can be used to create or guide the creation of various images and animations. A simple variation that uses a quadratic curve is the Fireworks program.


You can't see from the picture, but can if you run the program... this produces an animated 'firework' display made up of sparks in various shades of red and yellow, appearing to emanate from the ground, and making a roughly quadratic curve with their end points.
This program is based mainly on the Quadratic program discussed above, with a few differences. First, the code has been split into teo sections, using the built-in functions:
setup() and draw()

setup()
void setup(){
size(500,500); // screen area
frameRate(7);
}
As before the dimensionsof the viewing window are set. Additionally we set the frame rate for the animation. The number in the brackets signifies the speed of the animation – the higher the number, the faster the action.
draw()
The draw section includes mainly the same variables as were declared and initialised before, together with the loop that draws the curve. In this case we don't actually want to draw the lines that make up the curve, but we still need to make the calculation (enumerating the quadratic function) as the end points that produces will be used as a guide to the endpoints of the sparks.
Now a line is drawn to represent the spark from the middle of the bottom border, upwards to the point we just calculated with the equation. Left like that every spark would be of a neatly changing uniform length... but fireworks don't do that. So we add a small random amount to each spark so they are generally ending just off the curve.
Similarly, firework sparks are usually made up of several shades of various colours., In this case we use random intensities of randomly chosen red or yellow for the sparks.
Now we have one view of the sparks. To make it look more like a firework we use the fact that the loop code is in a draw function, together with the frameRate() function in the setup() function, to draw randomly sized curves of sparks, resetting the background to blank black in between curves.
What Next?
And that wraps up this first look at the drawing of curves. The topic will be explored in more depth later. For now this is all you need to draw poynomial curves of the first and second degree, and adjust their appearance. You have also seen how no curved lines at all go into the drawing of the curves. It's all achieved by drawing a series of straight lines set at slight angles to each other.



Quadratic
// 1st degree polynomial (quadratic)
// Basic formula: y = cxsquared + bx + a



// Initialise
float startX;
float startY;
float endX;
float endY;
size(500,500); // screen area
background(255,223,201);
boolean myColour = true;
int maxi = 10;

// set up offsets
int offsetx = width/2;
int offsety = height/5;

// make sure the curve fits the window height
int a = 1;
int b = 1;
int c = 1;

float stretchy = height/(c*pow(maxi, 2) + b*maxi + a);

// make sure the curve fits the window width
float stretchx = width/maxi/2;

// size of step through loop
int step = 1; // smoothness


// The drawing loop
startX = 0;
startY = height + 1;

for(int i = -maxi; i
// set end of this line (x and y coords)
endY = (c*i*i + b*i + a)*stretchy + offsety;
endX = i*stretchx + offsetx -1;

// change the colour of the this line
if (myColour == false){
myColour = true;
stroke(255,255,0);
}
else{
myColour = false;
stroke(0,0,255);
}

strokeWeight(5);
// draw the line
line(startX, startY, endX, endY);

// set start of next line to end of this one
startX = endX;
startY = endY;

}
Cubic
// 2nd degree polynomial (cubic)
// Basic formula: y = dxcubed + cxsquared + bx + a



// Initialise
float startX;
float startY;
float endX;
float endY;
size(500,500); // screen area
background(233,189,252);
boolean myColour = true;
int maxi = 10;

// set up offsets
int offsetx = width/2;
int offsety = height/2;

// make sure the curve fits the window height
int a = 1;
int b = 1;
int c = 1;
int d = 1;
float stretchy = height/(d*pow(maxi, 3) - c*pow(maxi, 2) + b*maxi + a);

// make sure the curve fits the window width
float stretchx = width/maxi/2;

// size of step through loop
int step = 1; // smoothness


// The drawing loop
startX = 0;
startY = 0;

for(int i = -maxi; i
// set end of this line (x and y coords)
endY = (d*i*i*i - c*i*i + b*i + a)*stretchy + offsety;
endX = i*stretchx + offsetx;

// change the colour of the this line
if (myColour == false){
myColour = true;
stroke(255,255,0);
}
else{
myColour = false;
stroke(0,0,255);
}

strokeWeight(5);
// draw the line
line(startX, startY, endX, endY);

// set start of next line to end of this one
startX = endX;
startY = endY;

}



Fireworks
// Fireworks
// Based on a first degree polynomial

void setup(){
size(500,500); // screen area
frameRate(7);
}


void draw(){
// Initialise
float startX;
float startY;
float endX;
float endY;
int maxi = round(random(1,200)); // don't allow zero value
boolean myColour = true;

// set up offsets
int offsetx = width/2;
int offsety = round(height/random(5,10));

// make sure the curve fits the window height
int a = 1;
int b = 1;
int c = 1;
float stretchy = height/(c*pow(maxi, 2) + b*maxi + a);

// make sure the curve fits the window width
float stretchx = width/maxi/2;

// size of step through loop
int step = 1; // smoothness

// The drawing loop
startX = 0;
startY = height + 1;
background(0);

for(int i = -maxi; i
// This time we don't draw the line that makes
// up part of the curve. Instead we just use
// the result of the formula evaluation to get
// the end coordinates, which will be the guide
// for the angle of the spark.
// Set end of this line (x and y coords)
endY = (c*i*i + b*i + a)*stretchy + offsety;
endX = i*stretchx + offsetx -1;

// draw a randomly red or yellow 'spark'
int strokeFlag = round(random(1, 100));
if (strokeFlag % 2 == 0){
stroke(255,255,round(random(1,100)));
}
else{
stroke(255,0,0 );
}

line(round(width/2), height, endX + random(1,50), endY - random(1,50));
}
}

1 comment:

  1. Hi, I've been trying to contact you about the Processing book that I'm going to attempt to publish, but I probably have an out-of-date email address for you. Could you contact me please? antonylees@gmail.com
    Cheers :)

    ReplyDelete