Random + Symmetry = Art
Category : Uncategorized
I have the nerdiest, artsiest Twitter feed. Every day I’m blown away by some creation by techy artists Saskia Freeke, Jessica “Nervous System” Rosenkrantz, Anders “Inconvergent” Hoff or somebody with the handle “Atticus Bones.” Atticus Bones often posts grids of randomly generated designs, and today’s really caught my attention:
I was inspired to clone these figures today. But how? There’s no way Atticus writes out the code for 484 designs; it must be done with random generation. I looked more closely at the individual designs for clues. I noticed every design has rotation symmetry. For instance, this design can be broken up into four identical quadrants.
I zoomed in on the top right quadrant:
Looking more closely at this quadrant, I could tell there’s more symmetry, reflectional symmetry about the diagonal. So we could chop it down even further to this:
So it’s just a matter of creating a random collection of segments, and reflecting them over the diagonal and then rotating them to the other quadrants. Some of Atticus’ figures give me a clue as to how many points would be needed to connect the segments. It seems to me there are “nodes” forming squares at a 45 degree angle. So in our example, here’s where I think we need nodes:
So we need 9 nodes, all with their own location. This is a job for a Node class, where each node has an x- and y-value and a distinguishing number “num.” I fired up the Python mode of the Processing graphics software:
I created a “nodes” list to store all the nodes, and a Grid class to draw a whole quadrant of nodes. Here are the first four nodes:
The vertical and horizontal distance between the nodes is “sz” (since “size” is already a keyword in Processing), so the x- and y-values of the nodes are easy to calculate.
Ready to Reflect
The nodes on the other side of the diagonal line needed their own locations. Was this going to be a mess? Not really. 5 out of the nine nodes are on the diagonal, so their locations don’t change. The other 4 took a little thinking and a little “back of the envelope” calculating. I didn’t have an envelope but I had a whiteboard. I drew a point (1, 2) and its reflection, which turned out to be (2,1):
So the pattern is the node at (x,y) is reflected to a node at (y, x). I easily made those changes after copying and pasting!
The next challenge was how to randomly draw a segment between two nodes (or not to). I wrote a list of the nodes and all the possible connections between them:
Now I created a list of randomly generated 0’s and 1’s that would determine whether each connection would be drawn or not:
self.connectChoice = [choice([0,1]) for x in range(16)]
Now a simple “if” statement would tell the program to draw the segment if the number in that spot is a 1.
def connect(self): #for every node that's connecting: for i,c in enumerate(self.connectChoice): if c == 1: #if the connection is active #connect the first node in the 2-list with the second nodes[connections[i]].connect(nodes[connections[i]]) #also connect the reflections of those nodes mirrornodes[connections[i]].\ connect(mirrornodes[connections[i]])
The “mirrornodes” part makes sure the reflection happens with the reflections of the nodes, too. So the quadrant is done! This code rotates the grid 90 degrees four times so we get our whole design:
def display(self): #copy the upper right quadrant # to all 4 quadrants by rotating for i in range(4): rotate(radians(90*i)) self.connect()
Now we need to scale down the size of each design and arrange a bunch of them in rows and columns. Sounds like we need a few loops!
#add the grid to rows and columns for j in range(22): for k in range(22): pushMatrix() #go to the location translate(j*6*sz, k*6*sz) #display 1 grid grids[k+22*j].display() popMatrix()
It took a little trial and error to get the right amount of spacing, but I finally had my “Atticus Clone:”
I posted it on Twitter and only got one like. But guess who it was from?
All my code is available on github. Have fun making Art!