Something I’d wanted to do for a long time was create a program that made nice configurable penrose tiles. If you need some background, Penrose Tiles are a aperiodic tiling of the plane, meaning they can be placed to infinity without a simple repeating pattern. Specifically I’ve always been partial to the tiling P3. Somewhere around this text should be an example of the program output. One thing I was particularly interested in was being able to add a border around tiles and configure its thickness. Unfortunately the way I have it now is not uniform. Its actually a ton of annoying trigonometry to get the borders right. But I’ll fix it sometime. The code for this was written in processing and here it is below.
//Program to generate Penrose Tiles (P3) through successive PShape fattriangle; PShape thintriangle; int sidelength; float scalefactor; int maxDepth; int padding; float padshift; void setup() { size(1500, 1000); // Size should be the first statement stroke(255); // Set line drawing color to black strokeWeight(1); //fill(255, 0, 0); background(255); noLoop(); sidelength = 3000; scalefactor = 1.0/sqrt(pow(1+cos(radians(72)),2) + pow(sin(radians(72)),2)); maxDepth = 9; padding = 200; } void drawFinalThinTriangle(int depth) { int rand = (int)(Math.random()*128); int rand2 = (int)(Math.random()*128); pushMatrix(); translate(-padding*pow(scalefactor,depth), 0); float newsidelength = sidelength-(padding)*sqrt(1+tan(radians(72))*tan(radians(72))); thintriangle = createShape(); thintriangle.beginShape(); //fill color thintriangle.fill(rand, rand2, 255); thintriangle.vertex(0, 0); thintriangle.vertex(-newsidelength*cos(radians(72)), -newsidelength*sin(radians(72))); thintriangle.vertex(-2*newsidelength*cos(radians(72)), 0); thintriangle.vertex(-newsidelength*cos(radians(72)), newsidelength*sin(radians(72))); thintriangle.vertex(0, 0); thintriangle.endShape(); //scale the triangle to the proper depth thintriangle.scale(pow(scalefactor,depth)); shape(thintriangle, 0, 0); //unscale the triangle //should probably just make a new one thintriangle.scale(pow(scalefactor,-depth)); popMatrix(); } void drawFinalFatTriangle(int depth) { int rand = (int)(Math.random()*128); int rand2 = (int)(Math.random()*128); pushMatrix(); translate(-padding*pow(scalefactor,depth)*cos(radians(36)), -padding*pow(scalefactor,depth)*sin(radians(36))); float newsidelength = sidelength-(padding)*sqrt(1+tan(radians(36))*tan(radians(36))); fattriangle = createShape(); fattriangle.beginShape(); //fill color fattriangle.fill(255, rand, rand2); fattriangle.vertex(0, 0); fattriangle.vertex(-newsidelength, 0); fattriangle.vertex(newsidelength*(cos(radians(108))-1), -newsidelength*sin(radians(108))); fattriangle.vertex(newsidelength*cos(radians(108)), -newsidelength*sin(radians(108))); fattriangle.vertex(0, 0); fattriangle.endShape(); //scale the triangle to the proper depth fattriangle.scale(pow(scalefactor,depth)); shape(fattriangle, 0, 0); //unscale the triangle ///should probably just make a new one fattriangle.scale(pow(scalefactor,-depth)); popMatrix(); } void drawThinTriangle(int depth) { if(depth >= maxDepth) { drawFinalThinTriangle(depth); } else { //increment depth depth += 1; pushMatrix(); //fuck this is ugly... //padding trig calculation translate(-2*sidelength*cos(radians(72))*cos(radians(72))*pow(scalefactor,depth), -2*sidelength*cos(radians(72))*sin(radians(72))*pow(scalefactor,depth)); rotate(radians(-108)); drawThinTriangle(depth); popMatrix(); pushMatrix(); translate(sidelength*cos(radians(108))*pow(scalefactor,depth-1), -sidelength*sin(radians(108))*pow(scalefactor,depth-1)); //translate(sidelength*(cos(radians(108)))*pow(scalefactor,depth), -sidelength*sin(radians(108))*pow(scalefactor,depth)); //fattriangle.scale(-1,1); rotate(radians(-108)); drawMirroredFatTriangle(depth); popMatrix(); } } void drawFatTriangle(int depth) { if(depth >= maxDepth) { drawFinalFatTriangle(depth); } else { //increment depth depth += 1; drawMirroredFatTriangle(depth); pushMatrix(); translate(-pow(scalefactor,depth)*sidelength, 0); drawThinTriangle(depth); popMatrix(); pushMatrix(); translate(sidelength*pow(scalefactor,depth-1)*(cos(radians(108))-1), -sidelength*pow(scalefactor,depth-1)*sin(radians(108))); //fattriangle.scale(-1,1); rotate(radians(-144)); drawFatTriangle(depth); popMatrix(); } } void drawMirroredThinTriangle(int depth) { if(depth >= maxDepth) { drawFinalThinTriangle(depth); } else { //increment depth depth += 1; pushMatrix(); //fuck this is ugly... translate(-2*sidelength*cos(radians(72))*pow(scalefactor,depth-1), 0); rotate(radians(108)); drawMirroredThinTriangle(depth); popMatrix(); pushMatrix(); rotate(radians(36)); drawFatTriangle(depth); popMatrix(); } } void drawMirroredFatTriangle(int depth) { if(depth >= maxDepth) { drawFinalFatTriangle(depth); } else { //increment depth depth += 1; pushMatrix(); translate(-pow(scalefactor,depth-1)*sidelength, 0); rotate(radians(144)); drawMirroredFatTriangle(depth); popMatrix(); pushMatrix(); translate(-pow(scalefactor,depth-1)*sidelength, 0); rotate(radians(72)); drawMirroredThinTriangle(depth); popMatrix(); pushMatrix(); translate(-pow(scalefactor,depth)*sidelength*cos(radians(36)), -pow(scalefactor,depth)*sidelength*sin(radians(36))); //fattriangle.scale(-1,1); //rotate(radians(-144)); drawFatTriangle(depth); popMatrix(); } } void draw() { translate(width+1500, height); //draw first triangle at 0 recursion depth drawFatTriangle(0); save("mypenrose.png"); }
So this code is super ugly. Why am I even publishing this? Well whatever. The code uses the inflation method to draw the tiles. You can read about this method in the linked page. Essentially the tiles have a scaling symmetry where one tile can be replaced with many. By repeatedly applying this to a single starting tile we can reach a nice tiling of the plane. The depth variable controls the number of inflations before the image is generated. You can play with this parameter, the padding, and the tile colors to make cool images.