LFMP7 Learning From Matt Pearson
Introduction Rules
The First Six Books of the Elements of Euclid in which coloured diagrams and symbols are used instead of letters for the greater ease of learners.
By Oliver Byrne,Surveyor of her Majesty‘s settlements in the Falkland Islands and author of numerous mathematical works. London, William Pickering, 1847.
The programs, from Matt Pearson’s publication I changed, date from 2022. I made the description of these programs in 2023 and 2024. The downside of describing work from two years ago is that you don’t remember exactly what you did at that time. Or how it was put together. So it is better to write everything down immediately while programming.
This publication is about autonomy. In politics, it is called self-governance. As an individual, it is self-reliance. It is the freedom and ability of an individual, organisation or nation, to make decisions independently. This seems simple but you see daily, in the world news, that this is often difficult.
The concept of autonomy appears in political, legal, philosophical, medical, moral, psychological, technical and physiological contexts. It also plays a role in the visual arts. There, autonomy means that the artist sees the work as an independent entity. The work of art is not a product or extension of its creator, but a stand-alone, autonomous thing.
The upcoming programs have animations as output. It means that output is completely self-contained. So there is hardly any influence from whoever wrote the program. There is only one important factor. It is the rules embedded in the program. Or maybe it’s rather game rules. You impose constraints. And within those constraints, one or more images emerge.
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.
LFMP_07_01_00
A cellular automata framework
The cellular automata grid is what we begin with in this publication. The program generates a grid with cells either on or off. Or black or white. Or alive or dead. They also refer to their neighbouring cells. Each cell bases its next state on the state of the cells surrounding it. New variations are generated with every mouse click.
Cell [][] _cellArray;
Initialisation of the two-dimensional array _cellArray. A two-dimensional array is really nothing more (or less) than an array of arrays.
int _cellSize = 10;
The size of the cells. In this case it is 10 pixels.
int _numX; int _numY;
Declare the variables _numX and _numY of type int.
size (500, 300);
The size of the display window.
_numX = floor (width / _cellSize);
Floor calculates the closest int value that is less than or equal to the value of the parameter. So _numX = 50. Floor (500 / 10).
_numY = floor (height / _cellSize);
Same as the previous remark. So _numY = 30. Floor (300 / 10).
_restart ();
Calls the function restart.
void restart ();
The restart function fills the 2D array with a grid of cells.
for (int x = 0; x < _numX; x++) { for (int y = 0; y < _numY; y++) {
After the grid is created, there is a second pass through the array to tell each of the cells who its neighbours are. Above, below, left, and right of them.
void draw () {
The draw loop then does a double pass through the array. The first pass triggers each cell to calculate its next state. And the second pass triggers each to make the transition and draw itself.
void mousePressed () {
Calls the restart function again.
class Cell {
This is the object.
boolean state; On or off.
Cell (float ex, float why) {
This block of code randomises the initial state.
void drawMe () {
This draws the cell.
LFMP_07_01_01
LFMP_07_01_02
LFMP_07_01_03
LFMP_07_01_04
LFMP_07_01_05
LFMP_07_01_06
LFMP_07_01_07
LFMP_07_01_08
int CellSize = 4; Reduced the size of the cells. And I also changed the names again according to Andrew Glassner’s advice. So _cellSize becomes CellSize when its a global variable and so on. I think that is more readable.
size (1000, 1000);
I also like working on a larger size display window. Beside of that, not much has changed.
int CellSize = 8; Cell size doubled.
rect (x, y, CellSize, CellSize); Ellipse replaced by rect.
int CellSize = 16; Apparently, I felt the need to double the size of each cell.
int CellSize = 32; Another doubling of the size of the cell.
int CellSize = 16; The size of the cell halved.
if (state == true) { strokeWeight (2); stroke (255); If state == true then we use a 2 pixels thick white line.
} else {
strokeWeight (4); stroke (128); In all other cases, we use a 4 pixel thick grey line.
rect (x - 6, y - 6, CellSize, CellSize); The correction of x and y – 6 makes everything fit nice in the display window. And this gives a totally different picture from previous black-and-white versions.
int CellSize = 32; The cell size doubled.
background (0); Blacked out the background. In fact, this is an enlargement of the previous program.
It looks like this file gives exactly the same output as the previous variation.
strokeWeight (4); stroke (128);
In drawMe doubled the line width. And replaced the white lines with grey lines. stroke (255);
Also in drawMe, below the } else { block, made the line colour white.
rect (x - 12, y - 14, CellSize, CellSize); Everything neatly moved up to make it all fit in the display window.
LFMP_07_01_09
LFMP_07_01_10
LFMP_07_01_11
LFMP_07_01_12
int CellSize = 16; Reduced the size of the cell by half.
strokeWeight (8);
In drawMe made the line thickness half as thick.
strokeWeight (4);
In the } else { block the line thickness was made four times thinner.
strokeWeight (16);
In drawMe the line thickness doubled.
ellipse (x + 2, y + 4, CellSize / 4, CellSize / 4);
Now continue with ellipses again. This gives a decorative pattern formed by large and small circles.
noStroke ();
In the drawMe block, we work with circles without outlines (I thought).
strokeWeight (8);
But still, I see that here the line thickness is halved.
strokeWeight (8);
Also in the } else { block, the line thickness has been made four times thicker. Everything else has remained the same.
strokeWeight (16);
In drawMe doubled the line thickness.
rect (x + 2, y + 2, CellSize / 4, CellSize / 4); And as a final variation anyway, back to rect‘s.
stroke (128);
The lines are fifty percent grey.
stroke (255);
In the } else { block coloured the lines white. So I see that I actually made most of the change in the drawMe function. The rest of the program remained intact.
LFMP_07_02_00
Conway‘s GOL
In the previous chapter, we used a framework with which we created a number of variations. The Game of Life (GOL) is a cellular automaton devised by the British mathematician John Horton Conway in 1970. It is a zero-player game, meaning that its evolution is determined by its initial state, requiring no further input.
The Game of Life is based on two rules. Rule 1: If a live (black) cell has two or three neighbours, it continues to live. Otherwise it dies, either of loneliness or overcrowding. Rule 2: If a dead cell has exactly three neighbours, a miracle occurs: it comes back to life.
void calcNextState () {
The calcNextState function adds the next state to the code that uses the GOL rules. This code first counts the number of neighbours alive and then applies GOL rules.
LFMP_07_02_01
LFMP_07_02_02
LFMP_07_02_03
LFMP_07_02_04
LFMP_07_02_05
LFMP_07_02_06
size (1000, 1000);
The only thing I have changed is the size.
int CellSize = 50;
Increased the cell size from 10 to 50.
rect (x - 20, y - 20, CellSize, CellSize); Replacing the ellipses with rect. For a more graphic image. The position had to be adjusted slightly to better fill the display window.
int CellSize = 4; Decreased the cell size from 50 to 4.
line (x, y, x + (CellSize * 2), y + CellSize); The lines cause a kind of panther pattern.
int CellSize = 16;
Increased the cell size from 4 to 16.
strokeWeight (32); strokeCap (SQUARE);
In the } else { block of the drawMe function, the line thickness increased to 32 pixels. StrokeCap (SQUARE) sets the style for rendering line endings. These ends are either squared, extended, or rounded, each of which specified with the corresponding parameters: SQUARE, PROJECT, and ROUND.
strokeWeight (8); I changed the line thickness of the outline for the ellipses to 8 pixels.
ellipse (x, y, CellSize, CellSize);
The ellipses overlap by about three-quarters. This creates an interesting pattern. It looks as if the black ellipses are hiding themselves in a field of white ellipses.
rect (x - 4, y - 4, CellSize, CellSize); Replacing the ellipses with rects. Gives a totally different picture. The x and y - 4 is necessary to fit everything nicely in the display window.
LFMP_07_02_07
LFMP_07_02_08
LFMP_07_02_09
LFMP_07_02_10
strokeWeight (2);
In the drawMe function after the } else { block, made the line thickness thinner. That means there is a grid in the background on which the black cells ‘play’.
stroke (128);
After the if statement, in the drawMe function, replaced black with grey.
strokeWeight (2);
All line thicknesses are now 2 pixels.
In my opinion, nothing has changed here.
Except the filename (I thought). The saveFrame function from two years ago showed me a different picture from what the same program in 2024 produced. So I had to recreate the program based on what the earlier saveFrame image showed me. That resulted in the following changes to the drawMe function.
strokeWeight (8); point (x + 4, y + 4);
I was working with points here. And since the thickness of a point is determined by strokeWeight, these grey dots will be 8 pixels thick.
strokeWeight (random (0, CellSize));
One small change. In the } else { block I used the random function to determine the thickness of the white points.
ellipse (x + 4, y + 4, CellSize / 2, CellSize / 2); I reused the random function again but now with ellipses instead of points. By the way, these cells never seem to ‘die out’.
LFMP_07_03_00
Peer pressure
A cellular automaton is an abstract structure. Each cell of the board has a state chosen from a finite set of states. The state of each cell changes with time, according to a uniform, deterministic rule, which takes into account the previous state of the cell itself and those in its neighbourhood.
The changes, however, happen synchronously, and in discrete time steps. The cell therefore looks at its neighbours to observe a trend. If the colour of the cell is in the majority, it remains unchanged. If it is in the minority, it changes colour.
void calcNextState () {
According to the Vichniac Vote (after Gerard Vichniac), every cell is sensitive to peer pressure. In the class Cell, the function calcNextState differs from the previous set of programs. First, it counts its neighbours including itself. Then it checks whether it is in the majority. If it is, the cell remains unchanged. If it is in the minority it changes colour. This makes the results look more like natural patterns.
LFMP_07_03_01
LFMP_07_03_02
LFMP_07_03_03
LFMP_07_03_04
LFMP_07_03_05
size (1000, 1000);
I increased the size to 1000 x 1000. Everything else has remained the same.
int CellSize = 4; The cell size reduced.
noStroke ();
In the drawMe function cancelled the outlines.
int CellSize = 8; The CellSize doubles.
stroke (255);
Used a white outline in drawMe.
noStroke (); fill (128);
In the } else { block turned off the outline and filled the cell with light grey.
int CellSize = 32; Made the cell size four times bigger.
fill (128);
Used a grey fill in the drawMe block.
fill (192);
In the } else { block used a slightly lighter grey fill.
rect (x, y, CellSize -1, CellSize - 1, 8); Replacing the ellipses with squares and rounded corners.
rect (x - 10, y - 10, CellSize -1, CellSize - 1, 8); The squares did not line up nicely in the display window. Hence the x – 10 and y – 10 correction.
LFMP_07_03_06
LFMP_07_03_07
LFMP_07_03_08
LFMP_07_03_09
LFMP_07_03_10
int CellSize = 16; The cell size cut in half.
ellipse (x + 4, y + 4, CellSize / 2, CellSize / 2); Dark grey ellipses brought back.
rect (x - 4, y - 4, CellSize -1, CellSize - 1, 4); The squares with rounded corners also reduced.
rect (x - 4, y - 4, CellSize, CellSize);
Removed the rounded corners. And kept the cell size at 16 which causes these to blend into each other.
noFill ();
stroke (255);
In the } else { block replaced fill with a white outline.
strokeWeight (4);
Thickened the lines in the } else { block.
int CellSize = 8; Cut the cell size in half.
fill (128, 64);
Added transparency in the drawMe function.
stroke (255, 128);
Added transparency in the } else { block as well. The end result makes the image appear blurred.
LFMP_07_04_00
Three state CA
LFMP_07_04_01
LFMP_07_04_02
LFMP_07_04_03
LFMP_07_04_04
Previous cellular automaton programs were all based on the on or off states. This program adds a third state: rest. So the rules are now as follows. If firing: next state is resting. If resting: next state is off. If off: and two neighbours are firing, next state is firing. The next remarks are comments which I copied from the program itself.
int nextState; State 1, 2 or 0.
if (neighbors [i].state == 1) { firingCount++;
The counting of firing neighbours.
if (firingCount == 2) {
nextState = 1; If two neighbours are firing, fire too.
nextState = state; Else don’t change.
} else if (state == 1) { nextState = 2; If just fired, rest.
} else if (state ==2) { nextState = 0; If rested, turn off.
fill (0);
} else if (state ==2) { Firing is black.
fill (150); Resting is grey.
fill (255); Off is white.
size (1000, 1000); I think a larger display window reflects the behaviour better.
int CellSize = 4; The smaller the cell size the more behaviour you get to see. As far as I can tell, nothing has changed here.
int CellSize = 8; Cell size doubled.
if (neighbors [i].state == 2) { In fact, I've changed 1 to 2.
if (firingCount == 1) {
And here 2 has changed to 1. So there is more frequent firing because the probability of one neigbour firing is higher than when two neighbours fire at the same time. It also means that the image does not stop changing.
LFMP_07_04_05
LFMP_07_04_06
LFMP_07_04_07
LFMP_07_04_08
LFMP_07_04_09
Three state CA
LFMP_07_04_10
int CellSize = 16; Cell size doubled again.
ellipse (x + 4, y + 4, CellSize, CellSize); The + 4 is to get everything nicely inside the display window.
ellipse (x + 4, y + 4, CellSize * 3, CellSize * 3); If the state is firing then the cell size is multiplied by 3.
ellipse (x + 4, y + 4, CellSize, CellSize); If the state is resting then the cell size remains the same.
ellipse (x + 4, y + 4, CellSize / 2, CellSize / 2); If the state is off then the cell size remains divided by 2.
int CellSize = 12; The cell size changed to 12.
fill (0, 64); Added transparency at the black firing cells.
ellipse (x + 2, y + 2, CellSize / 4, CellSize / 4); For cells that are off, the cell size is divided by 4.
ellipse (x + 2, y + 2, CellSize * 2, CellSize * 2); If a cell rests then the cell size is doubled.
rect (x + 2, y + 2, CellSize * 4, CellSize * 4); Ellipse replaced by rect.
rect (x + 2, y + 2, CellSize * 2, CellSize * 2); Ellipse replaced by rect.
rect (x + 2, y + 2, CellSize / 4, CellSize / 4); Ellipse replaced by rect.
int CellSize = 2; Cell size made six times smaller.
rect (x + 2, y + 2, CellSize * 12, CellSize * 4); Multiplying the cell size by 12 seems to give a shadow. I need to study this chapter again carefully because I think there is much more to be gained from it than what I have found now.
LFMP_07_05_00
CA with continuous behaviour
A cell is not limited to one, two or three behaviour rules. Matt Pearson explains that in the following program, a cell uses 255 behaviour rules. From white to grayscale and black. He has based this custom behaviour on standard physics model averaging. A chaotic state is affected by the cells neighbours.
These are the rules: If the average of the neighboring states is 255, the state becomes 0. If the average of the neighboring states is 0, the state becomes 255. Otherwise, new state = current state + neighborhood average – previous state value. If the new state goes over 255, make it 255. If the new state goes under 0, make it 0.
The following comments come directly from the program.
nextState = ((x / 500) + (y / 300)) * 14; Creates the initial gradient.
float average = int (total / 8); Here, the neighbourhood average is calculated.
lastState = state; Stores previous state.
fill(state);
Uses state value as fill color.
LFMP_07_05_01
LFMP_07_05_02
LFMP_07_05_03
LFMP_07_05_04
LFMP_07_05_05
size (1000, 1000);
As usual, increased the size. Other than that, I didn‘t change much.
int CellSize = 4; Reduced the cell size from 20 to 4.
noStroke ();
In the drawMe block stroke (0) changes to noStroke. Strangely, the ‘water’ then runs downwards.
CellArray [x][y].addNeighbor (CellArray [x][below]);
I thought how come this program shows a substantially different behaviour from the previous one. While I could not find any changes in the code. But in the restart function, I replaced y with x.
rectMode (CENTER);
Added in the drawMe function (CENTER). CENTER interprets the first two parameters of rect () as the shape‘s center point, while the third and fourth parameters are its width and height.
rect (x, y, CellSize * 250, CellSize);
The rects use only the left half of the display window to simulate ‘water’. That means horizontal lines on the right.
rect (x, y, CellSize, CellSize * 250); The water simulation at the top. And the bottom consists of vertical lines.
LFMP_07_05_06
LFMP_07_05_07
LFMP_07_05_08
LFMP_07_05_09
LFMP_07_05_10
fill (255, 128); All ellipses are filled with white that is semi-transparent.
ellipse (x, y + state, CellSize, CellSize); To y, the state is added. This produces strange patterns resembling rather pointed mountains.
rect (x + state, y + state, CellSize, CellSize);
In this, a rectangle is formed by squares. But the rectangle is repeatedly broken down and built up from the bottom right.
int CellSize = 16; Made the cell size four times larger.
stroke (state);
strokeWeight (4); This involves drawing a grid that eventually also simulates the shape of drops on water.
rect (x + 2, y + 4, CellSize, CellSize); Small correction to fit everything nicely in the display window.
strokeWeight (2);
rect (x + 32, y + 32, CellSize, CellSize); Added to the previous program. A kind of double grid that slowly disappears and rebuilds.
ellipse (x + 2, y + 4, CellSize, CellSize); ellipse (x + 32, y + 32, CellSize, CellSize); In fact, this is exactly the same program as the previous one. Only the rects have been replaced by ellipses.