LFMP4
Learning From Matt Pearson
Based on Matt Pearson’s book Generative Art
A practical guide using Processing
2023 Henk Lamers
Introduction 1
Sine, cosine and tangent
Hipparchus was a Greek astronomer, geographer, and mathematician who lived from –190 until –120. He is considered the founder of trigonometry, but is most famous for his incidental discovery of the precession of the equinoxes.
This publication is about drawing a circle in two ways. Once in the traditional way via the ellipse function in Processing. And the other way to draw a circle is by using trigonometry. Trigonometry is a part of mathematics that deals with triangles and in particular the trigonometric functions originally based on triangles such as sine, cosine and tangent.
Getty Images
Frankly, I knew less about trigonometry than Matt Pearson. I didn‘t know you could draw a circle made up of separate self-contained points by using sine and cosine. I even didn‘t know what sine and cosine were. Let alone that I would know the advantage of drawing a circle that way. So I went to find out what sine and cosine do. Let me introduce to you: ‘The unit circle’.
Now a strange association has surfaced. When I tell colleagues or interested parties about my ten-years journey to learn programming, I often get the comment: ‘Then you must know a lot about trigonometry’. As far as I am concerned, that is a misunderstanding.
Henk Lamers
Please note that all code in this publication refers to Matt Pearson's original code in his publication: Generative Art. A practical guide using Processing. 3
+y
1
2
+y
90º –x
0
+x
–x
0
180º
+x 360º
270º –y
3
–y
x=0 y=1
4
x=0 y=1
x = –1 y=0
vertical shift
x = 0.707
y = 0.707 45º horizontal shift
x=1 y=0
x = –1 y=0
x=1 y=0
210º y = – 0.5 x = – 0.866
x=0 y = –1
x=0 y = –1 5
6
180º sin (180) = 0 cos (180) = –1
90º sin (90) = 1 cos (90) = 0
270º sin (270) = – 1 cos (270) = 0
4
sin (70) = 0.939 cos (70) = 0.342
70º 360º sin (360) = 0 cos (360) = 1
Introduction 2
The unit circle
1 We are drawing an x and y coordinate plane. The vertical straight line is the y-axis. The horizontal straight line is the x-axis. The x-axis on the right is the positive x. The x-axis on the left is the negative x. The y-axis on the top is the positive y. The y-axis on the bottom is the negative y. All endpoints are one unit away from the centre. That is why it is called the unit circle. The unit circle has the centre in the midpoint of the coordinate axis. And it has a radius of one unit. Every point on the circle is exactly one unit away from its midpoint. 2 The point on the right side of the x line is zero degrees. The point on the top of the y line is 90 degrees. We are talking about angles. A half circle is 180 degrees. Threequarter of a circle is 270 degrees. The idea is that we started at zero degrees, and we‘re going anti-clockwise until we end at 360 degrees. Which is also zero. 3 What we are interested in is wherever we put a point on the circle what is the angle with the positive x-axis line. So x = horizontal shift. And y = vertical shift. So the point on the positive x-axis = zero degrees. x = 1 (horizontal shift) and y = 0 (vertical shift). Lets move to the 90 degrees position. x = 0 (horizontal shift). y = 1 (vertical shift). Let‘s move the point to the 180º. x = –1 (horizontal shift). y = 0 (vertical shift). If we move to the 270 degrees position on the circle. x = 0 (horizontal shift) and y = –1 (vertical shift). What about when the angle is 45 degrees? By using a calculator we end up at x = 0.707 (horizontal shift). y = also 0.707 (vertical shift). If you connect a horizontal line from that point to the vertical y-line its length is the same as the line from the point to the horizontal x-line. 4 Let‘s pick another point. For instance a point that sits at 210 degrees on the circle. The point is left from the center so it would be x = –0.866 (horizontal shift). And the y value that is y = –0.5. That is half of the vertical bottom y-line. Why am I talking about the horizontal and vertical shift as we choose points on the circle? What does that have to do with sine and cosine? I thought we’re talking about trigonometry triangles? Actually this is sine and cosine. The horizontal x shift at each angle on the unit circle is the cosine of that angle. And the vertical y-shift for any point on the unit circle is the sine of that angle. 5 So y = vertical shift = sine. x = horizontal shift = cosine. Sine and cosine are abbreviated as sin and cos. When we go back to our 45º degrees example the sin (45) = 0.707. And cos (45) = 0.707. Let's take an angle of 90º. Then sin (90) = 1. And cos (90) = 0. Another one. sin (180) = 0 and cos (180) = –1. Take a point on the bottom of the circle. That is 270º. sin (270) = –1. And cos (270) = 0. 6 In that manner we can calculate any point on the circumference. For instance lets take a point on 70º. sin (70) = 0.939. cos (70) = 0.342. And that is basically all that sine and cosine is doing.
5
The usual way to describe a circle in Processing with the ellipse function.
Both methods layered on top of each other.
6
The circle described with trigonometry in Processing using points.
Analysis Drawing a circle
LFMP_04_01_00
Drawing a circle in two fundamentally different ways
To be honest I was very curious as to why ‘the easy way’ to describe a circle is not good enough. But perhaps the way using trigonometry gives more possibilities. This first program (from Matt Pearson) shows the two ways. ellipse (centX, centY, radius * 2, radius * 2); This is the usual way to describe a circle. Processing‘s ellipse function draws an ellipse (oval) to the screen. An ellipse with equal width and height is a circle. By default, the first two parameters set the location, and the third and fourth parameters set the shape‘s width and height. The variables centX and centY give the centre of the circle. Radius * 2 is the distance from any point on the edge of the circle. In this program, it is 100. The circle is shown in grey in the display window via the ellipse function. So much for ‘the easy way' to draw a circle. The second way to describe a circle it is made up of black dots. The program calculates these points using trigonometry. for (float ang = 0; ang <= 360; ang += 5) { The variable ang (the angle) = 0. If ang is less than or equal to 360 then add 5 to ang. That results in 360 : 5 = 72 dots. float rad = radians (ang); The variable rad contains the radians. The trigonometric function radians converts a degree measurement to its corresponding value in radians. Radians and degrees are two ways of measuring the same thing. There are 360 degrees in a circle and 2 * PI radians in a circle. For example, 90° = PI / 2 = 1.5707964. All trigonometric functions in Processing require their parameters to be specified in radians. x = centX + (radius * cos (rad)); y = centY + (radius * sin (rad)); centX is 250 and centY is 150. The two form the centre of the circle. At each pass of the for loop, the variable rad is calculated via radius * cos (the horizontal distance from the circle). The variable rad is calculated via radius * sin (the vertical distance from the circle). Both numbers are added to centX and centY. That represents 1 point of the circle until 360 degrees is reached. By the way: Processing works anti-clockwise in degrees (from 0 > –90 > –180 > 270 > 360). point (x, y); Processing’s function point draws a point, a coordinate in space at the dimension of one pixel. The first parameter is the horizontal value for the point, the second value is the vertical value.
7
8
Variations
Thicken points using strokeWeight
Drawing a circle
LFMP_04_01_01
This is the same program as Matt Pearson‘s original. Only the size, background colour and radius of the circle have been enlarged. strokeWeight (20); Something I didn‘t know. You thicken points using the strokeWeight function. That seems a bit confusing. Maybe a new function ‘pointWeight’ would be helpfull in Processing? float radius = 400; In mathematics, the radius of a circle, sphere or cylinder is the distance from any point on the edge of the circle (or sphere, or cylinder) to its centre. The radius is half the diameter. Now, to make the ellipse, you need to have radius * 2 calculated. float lastx = -999; float lasty = -999; The variables lastx and lasty are not used. So I removed those.
LFMP_04_01_02
line (x, y, centerX, centerY); In his text, Matt says: ‘To complete the circle, you would draw lines connecting these points.’ I made a variation on that by drawing lines from the points to the centre. centreX and centreY were already present as variables. All that is needed is a line drawn from the x and y points to the centre. And this actually shows immediately why it is so important to calculate a circle via trigonometry. You can calculate from the centre any other point on the circle. But in addition, each point can be manipulated individually. This is not possible with Processing‘s ellipse function, which does not use points but four parameters to describe a circle.
LFMP_04_01_03
for (float ang = 0; ang <= 360; ang += 2) { Also interesting! When you keep ang <= 360 then the horizontal centre line is displayed twice. You can only see that this happens if you give the lines transparency. for (float ang = 0; ang <= 358; ang += 2) { To avoid that double line, I replaced ang <= 360 with ang <= 258. In that way it doesn‘t finish the complete circle but the problem is solved.
9
10
Variations
Turn a circle into a spiral
Drawing a spiral
To turn the circle into a spiral a small change in the code is required. And it is surprisingly simple. I could never have thought of it myself. LFMP_04_02_01
for (float ang = 0; ang <= 3900; ang += 5) { When you use ang <= 360 you will not get a full spiral. The spiral shape stops somewhere in the middle. At least with these settings. To get a nicely filled display window, I changed ang <= 360 to ang <= 3900. radius += 0.5; Actually, this is just a small adaptation of the previous circle program. Every run through the for loop increases the radius by 0.5. And that makes the x and y points move further and further away from the centre. Another example that wouldn‘t be possible with Processing‘s ellipse function.
LFMP_04_02_02
background (32); The grey of the background should be slightly darker. Otherwise, you won‘t see the dot effects in the spiral. stroke (255, 16); If you increase the transparency of the lines quite a bit, you get interesting dot effects all pointing from the dots towards the centre of the spiral. line (x, y, centerX, centerY); From each point on the spiral, you can also draw another line to the centre (centerX and centerY).
LFMP_04_02_03
for (float ang = 0; ang <= 3900; ang += 2) { radius += 0.2; The points in the spiral are even closer together here. This gives a very sophisticated effect. stroke (255, 4); This does have the consequence that transparency has to be extremely high if you want to continue to see as many dot effects as possible. There is a small flaw in this image. Looking at the last point in the spiral, it would actually be nicer if it were almost not visible. It seems that there is also an imaginary line forming from that point to the centre.
LFMP_04_02_04
for (float ang = 0; ang <= 3900; ang += 8) { radius += 0.8; To make the shape stand out a little more clearly, I made ang += 8. The radius has been increased to 0.8. The imaginary line from the last point to the centre has also disappeared. stroke (255, 16); Slightly less transparency has been applied. This creates new shapes on the outer and inner edges of the spiral.
11
12
Variations
Break that dullness
Adding noise
Such a spiral is, of course, mathematically and aesthetically a beautiful object. But it is also a bit boring and predictable. To break that dullness and predictability, noise is a good option. LFMP_04_03_01
float radiusNoise = random (2); The variable radiusNoise had the parameter 10. But I thought that was way too high. I lowered that to 2, which means it generates a float between 0 and 2. But in retrospect, this appears to have little effect. Whether you use 2, 10, 100 or 1000 at initialisation phase it doesn‘t make much difference to the final shape. At least I can‘t see the visual differences. radiusNoise += 0.01; The real differences in shape are made here. If you increase radiusNoise to, say, 10, you get a totally different shape. 0.01 Gives a sophisticated little change in the positioning of the points on the spiral. float thisRadius = radius + (noise (radiusNoise) * 50) - 10; The initialisation of the variable thisRadius. To find out exactly what happens here, I have made the initialisation a little simpler. float thisRadius = radius + radiusNoise; Results in a spiral with very little noise on the points. float thisRadius = radius + (noise (radiusNoise)); This gives almost the same result as the previous program line. float thisRadius = radius + (noise (radiusNoise) * 50); This amount of noise is already starting to look like something. The spiral now looks less predictable. float thisRadius = radius + (noise (radiusNoise) * 50) - 10; If you replace – 10 with – 1000, nothing remains of the spiral. Then there are tight straight lines running to the centre of the display window. Only in the corners is the spiral still visible. At – 500, the spiral shape comes back into view. And at – 10, it is all nicely balanced. So – 10 is more than a fine-tuning function. At least that‘s how I use it. Whether this was also Matt Pearsons‘s intention I do not know.
LFMP_04_03_02
radiusNoise += 0.04; radiusNoise raised from 0.01 to 0.04. This gives a slightly more ‘angular’ pattern in the areas where noise has been applied. You will have noticed that the last point in the spiral is clearly present because it is the last line drawn to the centre.
LFMP_04_03_03
radiusNoise += 0.16; Made radiusNoise four times higher than in the previous design. This causes a flower-like effect for the spiral. The connections between the points become even more extreme.
LFMP_04_03_04
radiusNoise += 0.64; This is where points on the spiral line really start to appear. Although the spiral line is also formed by dots, they are now more visible. The ‘flower effect’ remains clearly visible.
13
14
Variations Adding noise
LFMP_04_03_05
radiusNoise += 1.64; A rigorous operation. From 0.64 to 1.64. That‘s more than a doubling. float thisRadius = radius + (noise (radiusNoise) * 200) - 100; Time to fine-tune. noise (radiusNoise) multiplied by 200 – 100. That – 100 keeps the spiral nicely within the display window. But nothing is left of the spiral. It is more like an explosion.
LFMP_04_03_06
15
radiusNoise += 0.1; This is more of a return to the floral motif. Although I still find it a bit too coarse. Anyway... the point is clear. Trigonometry gives you more opportunities to make other shapes by moving points individually. This does make the image more interesting than the first spiral I drew.
16
Variations
Hundred spirals with noise
Hundred spirals
One of Matt Pearson‘s sayings: ‘If in doubt, this should always be your next step, multiply it all by hundred'. To what extent this actually works is the question. I, for instance, am always in doubt. And almost about everything, everywhere and anytime. LFMP_04_04_01
stroke (random (20), random (50), random (70), 80); When I first ran this code, I got a black area with some dark coloured lines in it quite vaguely. I then made the background white but I didn't like the colouring of the lines so I commented out that program line. I replaced the rule with stroke (255, 32); which means you always get grey lines and don‘t have to worry about using colour. int startAngle = int (random (360)); A choice of 360 degrees is made to start the spiral. int endAngle = 1440 + int (random (1440)); 1440 Is four times 360. I made 2880 out of both numbers and that increases the spiral that fills the display window better. float thisRadius = radius + (noise (radiusNoise) * 300) - 100; I replaced * 300 with * 250 and omitted – 100 altogether. Then the plane is nicely filled by the spiral. Although there is no longer a spiral visible. It is rather a randomly drawn number of lines drawn on top of each other. But it is interesting to note that it is only one line.
LFMP_04_04_02
int endAngle = 2250 + int (random (2250)); I‘ve replaced 1440 with 2250 (7 * 360). line (x, y, centerX, centerY); Again, added the lines to the centre. It provides some visual hold because they all end up in the centre. Otherwise it becomes very chaotic. And that again is something you don‘t want. Really? Why not?
LFMP_04_04_03
radiusNoise += 0.1; radiusNoise reduced to 0.1. It doesn't make much difference in terms of the image. But it seems to add a bit more complexity than the previous program.
LFMP_04_04_04
// line (x, y, centerX, centerY); This version is again a little closer to the initial stage. I commented out the lines from the spiral to the centre. But this version is less compact than the first version.
LFMP_04_04_05
radiusNoise += 0.42; No idea how I arrived at this number. Sometimes it's just a matter of trying, discarding and trying again. radius += 0.05; This number, combined with 0.42 of radiusNoise, is a kind of intermediate form. The spiral now uses the noise almost vertically on the spiral line. That gives a different picture anyway.
17
18
Variations
A circle drawn with a custom noise function
Custom noise
In the phase of this chapter, we use a custom noise function. This function calculates a return value. And that is applied to the circle. I have changed a few things about the program so that the output actually doesn‘t look at all like Matt Pearson‘s examples. No idea whether that‘s right or wrong. But I always see change as a positive element. LFMP_04_05_01
for (float ang = 0; ang <= 361; ang += 0.8) { This was 360. But using 361 solves the connection of the circle a little better. It‘s still not quite perfect but because I‘m using a thick outline, you can hardly see the break. Although it‘s still there at 360º. stroke (255, 160); A grey outline is generated here. I haven‘t used noFill () anywhere so this shape is automatically filled with white. strokeWeight (14); The outline has a thickness of 14 pixels. noiseValue += 10; In the original program, this was 0.1. radVariance = 100 * customNoise (noiseValue); And 100 was 30. float retValue = pow (sin (value), 3); The pow () function is an efficient way to multiply numbers by themselves in large quantities. The customNoise function returns the value of the sine multiplied by itself three times. This produces interesting self-replicating variations at the edge of the circle. In fact, this circle has long ceased to be a circle.
LFMP_04_05_02
for (float ang = 0; ang <= 361.55; ang += 0.759) { I am still trying to solve the connection on the right side of 360º. The number 361.55 seems to be the best to make the connection. But a slight discrepancy remains. endShape (CLOSE); Adding CLOSE also improved the connection slightly.
LFMP_04_05_03
for (float ang = 0; ang <= 361.55; ang += 0.6) { The number 0.6 seems appropriate for a slightly more sophisticated shape. noiseValue += 2; I am now trying to get the lines deeper and deeper into the centre of the object.
LFMP_04_05_04
stroke (255, 160); Removed the stroke. strokeWeight (14); Also removed the strokeWeight. This shape is much more interesting without an outline. It also looks like the error on 360º on the right has disappeared.
19
20
Variations Custom noise
LFMP_04_05_05
radVariance = 140 * customNoise (noiseValue); radVariance raised to 140, placing black lines deeper and deeper towards the centre. But in fact, the white lines make the black shapes. stroke (255); Too bad the lines give jaggies at certain angles, though. But I solved that by changing the stroke (255). strokeWeight (1); Does away with distracting jaggies in the image.
LFMP_04_05_06
noiseValue += 3; noiseValue raised to 3. radVariance = 300 * customNoise (noiseValue); The number 300 gives extreme coves in the circle.
LFMP_04_05_07
for (float ang = 0; ang <= 361; ang += 0.597) { I have been searching for an extremely long time to find a number that could yield near-perfect angles. So far, I came up with 0.597. I think this number produces the most ideal image. noiseValue += 2; noiseValue reduced from 3 to 2. radVariance = 150 * customNoise (noiseValue); And 150 seems a fine number for radVariance.
LFMP_04_05_08
21
for (float ang = 0; ang <= 3590; ang += 0.8) { Because I regularly make typos, sometimes a form comes up that makes me think: Why not? These variations arose because I had typed <= 3610 instead of 361. Then I changed ang += 0.8 to 0.9, 1.9, 2.9, 3.9 and 4.9. But above 200.9, it risks becoming less interesting.
22
Variations
Using the modulo function on a circle
Modulo
At this point, I got more into changing the code by changing the parameter value to determine the power to which you raise sine. This is exactly as written by Matt Pearson. And as always, I follow him in that. LFMP_04_06_01
int count = int ((value % 12)); Here, as far as I know, % (modulo) is used for the first time. I don‘t want to appear explanatory but I want to get this clear for myself. Modulo is a mathematical operation that finds the residual number when a whole number is divided by another. In Processing, it is represented by the symbol %. Here are a few examples: 11 % 4 = 3, because 11 can be divided twice by 4, leaving 3. 25 % 5 = 0, because 25 can be divided by 5 five times, leaving 0. 3 % 2 = 1, because 3 can be divided once by 2 and 1 remains. But why (value % 12) is used in the program that is puzzling to me. But as you increment 12 you get fewer and fewer spikes on the circle. Suppose you take (value % 1) then you get a circle without spikes.
LFMP_04_06_02
int count = int ((value % 94)); Oddly enough, when you raise this programme line to 94 it looks like fewer spikes have actually been added compared to the previous version.
LFMP_04_06_03
noiseValue += 128; noiseValue raised. radVariance = 128 * customNoise (noiseValue); redVariance raised. int count = int ((value % 168)); And set value to 168. That gives a curious circle with spikes to the inside and outside. It looks irregular but also not regular at the same time.
LFMP_04_06_04
strokeWeight (16); I wanted to try out what happens with thicker lines. noiseValue += 0.1; noiseValue brought down considerably. int count = int ((value % 128)); This also lowered slightly. But strange unexpected things happen on the circle shape. Small irregularities.
LFMP_04_06_05
stroke (255, 0, 0); I thought it was time to check via colour what exactly makes the shape. The red shape is generated by the curveVertex (x, y); The white fill is present by default if you don‘t turn it off via noFill. int count = int ((value % 512)); The number 512 is created by doubling 256. I always try to work with doublings as much as possible. Don‘t ask me why but I find it more systematic. But that‘s my abnormality. 256 : 2 = 128. 128 : 2 = 64, 64 : 2 = 32 and so on.
23
24
Variations Modulo
LFMP_04_06_06
fill (#00B0FF); I wanted to use a high-contrast colour and blue seemed a good one. A bit fierce but it fits nicely with this era. noiseValue += 1; Raised this number. radVariance = 54 * customNoise (noiseValue); No idea how I got to 54. I can‘t explain everything. But anyway if I run this program several times, the variations are minimal. But they are still there.
LFMP_04_06_07
float noiseValue = random (2); In this case, 2 was a fine choice. int count = int ((value % 4)); Again, the program gives very unexpected results. The red band varies with unpredictable thicknesses.
25
26
Variations
Case study: Wave Clock
Wave Clock
Matt Pearson's Wave Clock is a great example of how to use Perlin noise to create a natural shape. But what happened? I worked on this program for a while in order to analyse it. In fact, I turned off all the (Perlin) noise variables. That was the first version. And it already gave such beautiful images that I didn‘t change much else. Because these upcoming programmes use animation, I have used some screenshots that I thought were most relevant. LFMP_04_07_01
float TheAngle = - PI / 8; First I was confused by – PI. But as I look at it now, PI is clockwise animation. –PI is counterclockwise animation. The fact is when you use – PI / 8 more lines are being displayed than when you use – PI / 2. float StrokeColor = 255; This was in the original program 254. I have not been able to find out why. So I changed it to 255. AngleNoise = random (1); RadiusNoise = random (1); X_Noise = random (1); Y_Noise = random (1); The initialisation of these variables were all set to 10. Changed them to 1. Not that it mattered. RadiusNoise += 0.001; In the beginning this was set at 0.005. But I thought that was 0.004 too high. TheRadius = (noise (RadiusNoise) * 1000) + 1; Here was 1000 the number 550. AngleNoise += 0.001; This was 0.005 in the original program. X_Noise += 0.001; Y_Noise += 0.001; These were originally both 0.01. // float centerX = width / 2 + (noise (X_Noise) * 100) - 50; // float centerY = height / 2 + (noise (Y_Noise) * 100) - 50; Commented out these two lines. stroke (StrokeColor, 64); A minimal change from 60 to 64. Not that it matters much visually but it is to reassure myself. strokeWeight (2); I found the line thickness a little too thin.
27
28
Variations Wave Clock
LFMP_04_07_02
RadiusNoise += 1.0; Was 0.001. RadiusNoise stands for the length of lines drawn. TheRadius = (noise (RadiusNoise) * 20000) + 1; 20000 was 1000. 20000 is the minimum length + 1 is the maximum length and a random length is chosen in between. AngleNoise += 0.0; I would prefer as little noise as possible. So this is completely off. And that was the intention. I wanted perfect noiseless lines that give a tight image and constantly renew themselves. This gives you very distinct tessellations from the centre. // X_Noise += 0.001; // Y_Noise += 0.001; Commented out these two lines.
LFMP_04_07_03
RadiusNoise = random (2); AngleNoise = random (2); X_Noise = random (2); Y_Noise = random (2); These were all at 1. RadiusNoise += 0.1; Lowered from 1.0. TheRadius = (noise (RadiusNoise) * 700) + 1; This was set at 20000 in the previous version. TheAngle += (noise (AngleNoise) * 3) - 3; Brought down from * 6 to * 3. And those are actually the few changes I made in order to get different images and animation.
29
30