| Generative Art
A practical guide using Processing
Matt Pearson
If you draw a pentagon, then plot the midpoints of each of its five sides and extend a line perpendicular to each of these points, you can connect the end of these lines to make another pentagon. In doing this, the remaining area of the shape also ends up being subdivided into further pentagons, meaning that within each pentagon are six subpentagons. And within each of those, another six sub-pentagons, repeated downward toward the infinitesimal. In this article, based on chapter 8 of Generative Art, author Matt Pearson explains how exactly to do that.
To save 35% on your next purchase use Promotional Code pearson0835 when you check out at www.manning.com.
You may also
be interested in…
|
In 2008 I went to a meeting of the Computer Arts Society (CAS)
in London, an organization that was (remarkably, for a body devoted to
computing) celebrating its fortieth anniversary with that event. There, I heard
a short talk by the society’s initiator and first chairman, the artist and
mathematician Alan Sutcliffe.[1] Through this talk, he introduced me to a
marvelous shape, which I have since mentally dubbed the Sutcliffe
Pentagon.
To be clear, I’ve spoken to Alan about this, and he isn’t
entirely over the moon with me using this name. He has been insistent that,
even though he thought it up, he doesn’t believe the shape was his invention.
He cites Kenneth Stephenson ’s work on circle packing[2] as
preceding him, in which Stephenson himself credits earlier work by Floyd,
Cannon, and Parry.[3] But I have since reviewed this trail of influence,
and, although Sutcliffe’s predecessors do describe a dissected pentagon, none
of them take the idea half as far as he did. And even if they had, it wouldn’t
change the way the Sutcliffe Pentagon was burned into my brain that particular
evening.
So, respecting Alan’s modesty, and his proviso that we probably
shouldn’t, strictly speaking, refer to this construct as a Sutcliffe Pentagon,
let me begin by showing you what a Sutcliffe Pentagon looks like (see figure 1).
Then, I’ll talk you though how to construct a Sutcliffe Pentagon, and finally
I’ll show you a number of different Sutcliffe Pentagon–derived works, developed
using the Sutcliffe Pentagon structure.
Figure 1 Sutcliffe pentagons
Sutcliffe noticed that if you draw a pentagon, then plot the
midpoints of each of its five sides and extend a line perpendicular to each of
these points, you can connect the end of these lines to make another pentagon.
In doing this, the remaining area of the shape also ends up being subdivided
into further pentagons, meaning that within each pentagon are six subpentagons.
And within each of those, another six sub-pentagons, repeated downward toward
the infinitesimal.
If you don’t find yourself overly thrilled by the mathematics of
this yet, bear with me. In the following section, I’ll show what you can do
with this simple principle. Let’s start by coding it up; then, I’ll demonstrate
some of the directions I took it in.
Construction
You begin by drawing a regular pentagon. But the way I
approached it, you don’t just draw five lines between five points; instead, you
create a drawing method that is a little more generic—rotating 360 degrees
around a center and extrapolating points at certain angles. For example, if you
plot a point every 72 degrees, you get a pentagon.
Listing 1 Drawing a pentagon using rotation
FractalRoot pentagon;
int _maxlevels = 5;
void setup() {
size(1000, 1000);
smooth();
pentagon = new FractalRoot(); #A
pentagon.drawShape(); #B
}
class PointObj {
float x, y;
PointObj(float ex, float why) { #C
x = ex; y = why;
}
}
class FractalRoot {
PointObj[] pointArr = new PointObj[5];
Branch rootBranch;
FractalRoot() {
float centX = width/2;
float centY = height/2;
int count = 0;
for (int i = 0; i<360; i+=72) {
float x = centX + (400 * cos(radians(i)));
float y = centY + (400 * sin(radians(i)));
pointArr[count] = new PointObj(x, y);
count++;
}
rootBranch = new Branch(0, 0, pointArr);
}
void drawShape() {
rootBranch.drawMe();
}
}
class Branch {
int level, num;
PointObj[] outerPoints = {};
Branch(int lev, int n, PointObj[] points) { #D
level = lev;
num = n;
outerPoints = points;
}
void drawMe() {
strokeWeight(5 - level);
for (int i = 0; i < outerPoints.length; i++) { #E
int nexti = i+1;
if (nexti == outerPoints.length) { nexti = 0; }
line(outerPoints[i].x, outerPoints[i].y,
outerPoints[nexti].x, outerPoints[nexti].y);
}
}
}
#A Creates root pentagon
#B Calls drawShape method on it
#C Stores x,y position
#D Constructs Branch object
#E Branch draws itself
The FractalRoot
object contains an array of five points. It fills that array by
rotating an angle around a center point in 72-degree jumps. It then uses that
array of points to construct the first Branch object. The Branch object is
your self-replicating fractal object. Each branch draws itself by connecting
the points it has placed in its outerPoints array.
This should give you a pentagon, as shown in figure 2.
Figure 2 Just in case you don’t know what a regular
pentagon looks like
Next, you plot the midpoints of each side. Add the two functions
in the following listing to the Branch object.
Listing 2 Functions to calculate the midpoints of a
set of vertices
PointObj[] calcMidPoints() {
PointObj[] mpArray = new PointObj[outerPoints.length];
for (int i = 0; i < outerPoints.length; i++) {
int nexti = i+1;
if (nexti == outerPoints.length) { nexti = 0; }
PointObj thisMP = calcMidPoint(outerPoints[i],
outerPoints[nexti]);
mpArray[i] = thisMP;
}
return mpArray;
}
PointObj calcMidPoint(PointObj end1, PointObj end2) {
float mx, my;
if (end1.x > end2.x) {
mx = end2.x + ((end1.x - end2.x)/2);
} else {
mx = end1.x + ((end2.x - end1.x)/2);
}
if (end1.y > end2.y) {
my = end2.y + ((end1.y - end2.y)/2);
} else {
my = end1.y + ((end2.y - end1.y)/2);
}
return new PointObj(mx, my);
}
The first function, calcMidPoints, iterates through the array
of outer points and passes each pair of points to the second function, calcMidPoint;
this function works out the difference between each individual pair, covering
every possible relative position to each other. calcMidPoints collects each of the results
in an array to pass back.
You call the new function in the Branch constructor and put the result in
an array named midpoints:
PointObj[] midPoints = {};
Branch(int lev, int n, PointObj[] points) {
…
midPoints = calcMidPoints();
}
This gives you figure 8.12.
Figure 3 Calculating the midpoints of the sides
The next set of points you need are the ends of the struts to
extend from the midpoints. You don’t need to work this out relative to the
angle of the side vertex; you can instead use one of the opposite points of the
shape to aim toward.
First, define a global _strutFactor variable to specify the
ratio of the total span you want the strut to extrude:
float _strutFactor = 0.2;
Now, the Branch
object needs another set of functions to plot those points.
Listing 3 Functions to extend the midpoints toward
the opposite points
PointObj[] calcStrutPoints() {
PointObj[] strutArray = new PointObj[midPoints.length];
for (int i = 0; i < midPoints.length; i++) {
int nexti = i+3;
if (nexti >= midPoints.length) { nexti -= midPoints.length; }
PointObj thisSP = calcProjPoint(midPoints[i], outerPoints[nexti]); #A
strutArray[i] = thisSP;
}
return strutArray;
}
PointObj calcProjPoint(PointObj mp, PointObj op) {
float px, py; float adj, opp;
if (op.x > mp.x) {
opp = op.x - mp.x;
}else{
opp = mp.x - op.x;
}
if (op.y > mp.y) { #B
adj = op.y - mp.y;
}else{
adj = mp.y - op.y;
}
if (op.x > mp.x) {
px = mp.x + (opp * _strutFactor);
} else {
px = mp.x - (opp * _strutFactor);
}
if (op.y > mp.y) { #C
py = mp.y + (adj * _strutFactor);
} else {
py = mp.y - (adj * _strutFactor);
}
return new PointObj(px, py);
}
#A Projects midpoint to opposite outer point
#B Trig calculations
#C Projects along hypotenuse
Again, add this code to the Branch constructor:
PointObj[] projPoints = {};
Branch(int lev, int n, PointObj[] points) {
...
projPoints = calcStrutPoints();
}
And plot those points in the drawMe function so you can see they’re
correct:
strokeWeight(0.5);
fill(255, 150);
for (int j = 0; j < midPoints.length; j++) {
ellipse(midPoints[j].x, midPoints[j].y, 15, 15);
line(midPoints[j].x, midPoints[j].y,
projPoints[j].x, projPoints[j].y);
ellipse(projPoints[j].x, projPoints[j].y, 15, 15);
}
You should be looking at something like figure 4.
Figure 4 Project struts toward the opposite points
Now you have all the points you need for the recursive
structure. You can test this first by passing the five strut points as the
outer points for an inner pentagon. Add the following to the Branch object’s
constructor:
Branch[] myBranches = {};
Branch(int lev, int n, Point[] points) {
...
if ((level+1) < _maxlevels) {
Branch childBranch = new Branch(level+1, 0, projPoints);
myBranches = (Branch[])append(myBranches, childBranch);
}
}
Then, add a similar trickle-down method to the drawMe function
so the children are rendered to the screen:
void drawMe() {
...
for (int k = 0; k < myBranches.length; k++) {
myBranches[k].drawMe();
}
}
You should see the result shown in figure 5.
Figure 5 A recursive pentagon
Adding the other five pentagons to create a complete Sutcliffe
Pentagon fractal is just a matter of passing the points to five more new Branch objects.
The code is as follows:
if ((level+1) < _maxlevels) {
...
for (int k = 0; k < outerPoints.length; k++) {
int nextk = k-1;
if (nextk < 0) { nextk += outerPoints.length; }
PointObj[] newPoints = { projPoints[k], midPoints[k],
outerPoints[k], midPoints[nextk], projPoints[nextk] };
childBranch = new Branch(level+1, k+1, newPoints);
myBranches = (Branch[])append(myBranches, childBranch);
}
}
This gives you the fractal shown at the beginning of this
section in figure 1. Now you can begin experimenting with it.
Exploration
You have the ratio of the projected strut in a handy variable,
so let’s start by varying this. You’ll add a draw loop to redraw the fractal every
frame with a new strut length, and vary that length by a noise factor. For this
modification, you don’t need to do anything to the objects—just update the
initialization section of the code as shown in the following listing.
Listing 4 Varying the strut length of a Sutcliffe
Pentagon
FractalRoot pentagon;
float _strutFactor = 0.2;
float _strutNoise;
int _maxlevels = 4; 3A
void setup() {
size(1000, 1000);
smooth();
_strutNoise = random(10);
}
void draw() {
background(255);
_strutNoise += 0.01;
_strutFactor = noise(_strutNoise) * 2;
pentagon = new FractalRoot(frameCount);
pentagon.drawShape();
}
#A Reduced to 4 to keep manageable
You make one other little change here: you pass the frameCount to the
FractalRoot object.
If you then modify FractalRoot,
you can use that number to increment the angle and make the shape spin, Space
Odyssey style:
FractalRoot(float startAngle) {
...
float x = centX + (400 * cos(radians(startAngle + i)));
float y = centY + (400 * sin(radians(startAngle + i)));
...
}
Also notice that the strut ratio doesn’t just vary between 0 and
1, we’ve allowed it to go to 2, so it can extend beyond the limits of the
shape. Does this work? Try it and see. How about if the strut ratio went into
negative values? What would happen then? Only one way to find out:
_strutFactor = (noise(_strutNoise) * 3) - 1;
In this case, the ratio varies between -1 and +2. You end up
with an animation that produces shapes like those shown in figure 6.
Figure 6 Strut factors -1 through to +2
Already it’s getting interesting.
Although the pentagon has a neat symmetry, there is no reason why
your starting shape has to have five sides. One of the reasons I adopted the
non-absolute method of drawing the pentagon was that it made it easy to add
extra sides to the polygon. For example, create a global variable:
int _numSides = 8;
Now, change the FractalRoot class as shown in this listing.
Listing 5 Sutcliffe Pentagon, not limited to five
sides
class FractalRoot {
PointObj[] pointArr = {};
Branch rootBranch;
FractalRoot(float startAngle) {
float centX = width/2;
float centY = height/2;
float angleStep = 360.0f/_numSides; #A
for (float i = 0; i<360; i+=angleStep) {
float x = centX + (400 * cos(radians(startAngle + i)));
float y = centY + (400 * sin(radians(startAngle + i)));
pointArr = (PointObj[])append(pointArr, new PointObj(x, y));
}
rootBranch = new Branch(0, 0, pointArr);
}
void drawShape() {
rootBranch.drawMe();
}
}
#A Steps according to number of sides
Now you have a structure capable of handling any number of
sides. How about eight, as shown in figure 7?
Figure 7 Eight sides: a Sutcliffe Octagon?
Or 32? (See figure 8.)
Figure 8 A 32-sided structure
Taking another approach, why should you feel limited to 360
degrees? I discovered that setting the angle step to 133 and stepping 8 times
(effectively turning the circle three times) gave me something like figure 9.
Figure 9 Angle step of 133, through 1024 degrees
I followed this line of exploration by making the angles and the
number of sides vary according to a noise factor between frames, in the same
way you varied the strut factor. You shouldn’t have any difficulty working out
the code if you want to try it. Through this process, I discovered what an
incomplete revolution looks like (see figure 10).
Figure 10 The system is resilient enough to work with
irregular and incomplete shapes.
The fractal structure I built (to Sutcliffe’s specification)
proved to be extremely mathematically sturdy, as long as I didn’t turn the
number of levels up too high. This meant I could wrench it about as if I were
throttling a ferret, with the only consequence being that the results became less
linear and more interesting.
Summary
Recursive fractal constructions are at their root an idea stolen
from natural organization. Despite how it may look when viewed through a
screen, we don’t live in a digital world. Our reality is stubbornly analog: it
doesn’t fit into distinctly encodable states. It’s an intricate, gnarly, and indefinite
place. If we want to use digital tools to create art, which somehow has to
reflect our crazy, chaotic existence, we need to allow imperfection and
unpredictability into the equation. But, as I hope I’ve shown, there is room
for the organic within the mechanical, and programming isn’t just about
efficiency and order. Our computing machines, which may only be capable of
producing poor imitations of life, are tapping a computational universality
that the natural world shares.
Programming generative art, if I were to try and sum up it up
into a single pocket-sized nugget, is about allowing the
chaos in. Not all things in life benefit from a structured approach;
there is much that is better for a little chaotic drift. If you have a way of
working that allows this freedom, permitting inspiration to buffet you from
idea to idea, you’ll inevitably end up in some interesting places.
Here
are some other Manning titles you might be interested in: