Conway game of life 3D sketch below!
Concept:
The inspiration for this is Conway’s game of life, computer generated art is like an umbrella term, I started with searching up old computer generated art in the 50’s and 60’s, and there were some cool concepts like using computer parts to make some sort of art out of them, but it wasn’t exactly what I was looking for. Then it hit me! Conway’s game of life, but not just 2D, that would not work for me, I had to make it 3D, and that’s how this sketch started.
I had to tweak the rules a bit to give this a more generative art feel as well as per-determining the spawn position for the starting cells.
Implementation:
Before we get into the technical implementation of this, let me cover the theory and rules that the sketch runs by.
Of course for 3D we had to use webgl, to get our 3rd axis (z), and also use orbital control to allow us to move around the area.
createCanvas(400, 400, WEBGL); // Start with a view of the entire resolution camera(0, 400, 4900, 0, 0, 0, 0, 1, 0); orbitControl();
A dead cell becomes alive only if it has exactly 3 or 6 neighbours. (Neighbours are alive cells that are next to the cell we are checking)
A living cell stays alive only if it has exactly 5 or 6 neighbours, otherwise it will die.
For the starting spawn, I first started with giving each cell a random chance of 2% to become alive on initialization, it would work however sometimes the cells would all die or I would not get a good looking design, so I decided to spawn the cells at each corner.
// Create the array for all th cells state
for (let x = 0; x < res; x++) {
grid[x] = [];
next[x] = [];
for (let y = 0; y < res; y++) {
grid[x][y] = [];
next[x][y] = [];
for (let z = 0; z < res; z++) {
// Seed with 3x3x3 clusters at corners
let inCorner1 = x < 3 && y < 3 && z < 3;
let inCorner2 = x >= res - 3 && y < 3 && z < 3;
let inCorner3 = x < 3 && y >= res - 3 && z < 3;
let inCorner4 = x >= res - 3 && y >= res - 3 && z < 3;
let inCorner5 = x < 3 && y < 3 && z >= res - 3;
let inCorner6 = x >= res - 3 && y < 3 && z >= res - 3;
let inCorner7 = x < 3 && y >= res - 3 && z >= res - 3;
let inCorner8 = x >= res - 3 && y >= res - 3 && z >= res - 3;
grid[x][y][z] =
inCorner1 ||
inCorner2 ||
inCorner3 ||
inCorner4 ||
inCorner5 ||
inCorner6 ||
inCorner7 ||
inCorner8
? 1
: 0;
next[x][y][z] = 0;
}
}
}
To check for neighbours of each cell, we use a triple nested loop and offset from -1 to 1, to check behind, center forward for each axis.
A couple things to note is, I run the rule checking code once every 30 frames, so that the cells don’t populate too fast and so that we can actually see what is happening and enjoy the chaos that is happening.
A couple things I am proud of is my optimization and coordinate calculation.
Context: There are 2 grids that we are using, our “present” grid and the “next” grid, to not confuse the computer, we apply our rule application and calculation to our present grid, but store the results in our next grid, now originally to switch between the 2 I would turn grid into a long string JSON, then parse it tot equate it to next, but that would lead to thousands of operations. So what I figured out was actually something from c++ and dealing with pointers, and it’s simply just changing the name. To provide context, in javascript you can’t equate the 2 grids to each other to change them, because then they would be connected and if you affect one grid the other is also affected which defeats the purpose of having 2 grids.
let temp = grid; grid = next; next = temp;
These 3 lines may not seem much, but it saves our computers from doing thousands of operations every 30 frames, and reduces those thousand of operations to simply 2 operations.
Now, I had to limit the cells to spawn in a specific area of our canvas, otherwise the calculations would get too much, and wordpress would not be able to handle that many calculations.
let cellSize = 50; let res = 20;
Resolution is basically how many cubes we want, or in this case we want a dimension of a 20 by 20 by 20 cubes, which is 8000 cubes, and 8000 operations every 30 frames, anymore and the browser would slow down tremendously so 20 was the sweet spot.
for (let x = 0; x < res; x++) {
for (let y = 0; y < res; y++) {
for (let z = 0; z < res; z++) {
// Check if the cell is alive
if (grid[x][y][z] === 1) {
// Calculate the position of the cell in the canvas using the res and size as reference.
let xPos = x * cellSize - (res * cellSize) / 2;
let yPos = y * cellSize - (res * cellSize) / 2;
let zPos = z * cellSize - (res * cellSize) / 2;
push();
translate(xPos, yPos, zPos);
// Map scales our rgb colors based on the location so the cube looks like a spectrum.
let r = map(x, 0, res, 0, 255);
let g = map(y, 0, res, 0, 255);
let b = map(z, 0, res, 0, 255);
fill(r, g, b);
stroke(0, 50);
box(cellSize);
// Take the pointer back to 0,0,0
pop();
}
}
}
}
Our coordinate system of the resolution is going to be different to the canvas coordinate system, so with a little bit of math, we could take our raw x y z coordinate and convert them into our resolution coordinates to allow us a proper bounded area.
Finally giving us that beautiful color spectrum, we use the map functions which allows us to scale our resolution with the rgb values, for example at 10 (half way into resolution) the r value would be 127 (half of 255).
Reflection and Improvements:
Honestly I am particularly happy about how this turned out, I thought it would be quite difficult to implement but it turned out a lot better and easier than I expected it to and I am happy that I went through with it.
A couple ways I would think about improving it is adding a gradient color background, and maybe implement more shapes for cells to be rather than simply a cube.