## Menger Fractal

#### This web page displays the results of the use of writing python scripts that create a Menger fractal.

 Above is the beauty shot rendered with Renderman 19, made by alterations of the Menger Sponge super class codes. Details of the simple scene created by using RIB Archives.
###### Concept Inspiration

My inspiration is from Nintendo's Super Mario Bros (1985). I chose this concept because the look of heavy 1980s pixel graphics could belong well to the same world as fractal blocks. I thought the medium could translate well from 2D to 3D for the purpose of this project.

###### Shapes Creation

 ``` -----------------------------------------------Bushes & Clouds------------------------------------------------ --------------------------------------------------------------------------------------------------------------- # Below is the implementation of using ribs and random deletion to create shapes such as the bushes and clouds def delete(self, boxes): num = Menger3DRib.num_deletions rand_ints = [int(26*random.random()) for i in xrange(num)] # Remove duplicates using a dictionary dict = {}; for num in rand_ints: if num < 27: dict[num] = num self.holes = sorted(dict.keys(), reverse=True) return super( Menger3DRib, self).delete(boxes) def shapeSphere(self, p1, p2): x = p1[0] - p2[0] y = p1[1] - p2[1] z = p1[2] - p2[2] return math.sqrt(x*x + y*y + z*z) def write(self): f = open(self.path,'w') x,y,z,X,Y,Z = self.bbox f.write('#bbox: %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f\n' % (x,y,z,X,Y,Z)) origin = (0.0,1.0,0.0) readArc = '/home/micgao20/mount/stuhome/maya/projects/Menger/scenes/lego.rib' for box in self.retained: xMi, yMi, zMi, XMa, YMa, ZMa = box distSphere = self.shapeSphere(box, origin) if distSphere <= random.uniform(2.1, 3.0): f.write(ri_utils.RibArc(box, readArc)) f.close() ```
 ``` --------------------------------------------------Pipes-------------------------------------------------------- --------------------------------------------------------------------------------------------------------------- def shapeCylinder(self, p1, p2): x = p1[0] - p2[0] y = p1[1] - p2[1] z = p1[2] - p2[2] return math.sqrt(x*x + z*z) def write(self): f = open(self.path,'w') f.write('#bbox: %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f\n' % (self.bbox[0], self.bbox[1], self.bbox[2], self.bbox[3], self.bbox[4], self.bbox[5]) ) origin = (0.0,1.0,0.0) for box in self.retained: distCylinder = self.shapeCylinder(box, origin) if distCylinder <= 1.0 and distCylinder >=0.9: f.write(ri_utils.Cube(box)) f.close() ```

###### Menger Alterations

Above are variations of the Menger Sponge created by overriding numbers and order of retained and deleted holes.

The classic 3D Menger Sponge.

###### Menger 3D Codes

 ``` -------------------------------------------------Super Class--------------------------------------------------- --------------------------------------------------------------------------------------------------------------- import ri_utils, random class Menger3D(object): defaultHoles = [22,16,14,13,12,10,4] #defaultHoles = [22,16,14,13,12,10,4] def __init__(self, holes, bbox, num): """ The constructor requires a list of numbers in the range 0 to 26 that identify which cubes will be considered to be holes.""" # If the "holes" list is empty we use the default list if(len(holes) > 0): self.holes = holes else: self.holes = Menger3D.defaultHoles # default self.retained = [] # list of non-deleted cubes self.deleted = [] # list of the deleted cubes self.divide(bbox,num) #_______________________________________________________ # Given the minimum x,y,z and maximum x,y,z coordinates # of a bounding box this proc returns the bouding box # coordinates of a "row" of three boxes. def row(self, x0,y0,z0, w,h,d): x,y,z = x0,y0,z0 X,Y,Z = x + w, y + h, z + d boxes = [] for n in range(3): box = [x,y,z, X,Y,Z] boxes.append(box) z,Z = z + d, Z + d return boxes #_______________________________________________________ # A recursive proc that subdivides a bounding box into # 27 sub-cubes. Each time the proc is called the arg # "depth" is decremented. Recursion terminates when its # value becomes zero. def divide(self, bbox, depth): if depth == 0: self.retained.append(bbox) return [] x0,y0,z0,x1,y1,z1 = bbox w = float(x1 - x0)/3 h = float(y1 - y0)/3 d = float(z1 - z0)/3 x,y,z = x0,y0,z0 boxes = [] for layer in range(3): x = x0 for rows in range(3): boxes.extend(self.row(x,y,z,w,h,d)) x = x + w y = y + h boxes = self.delete(boxes) # Recursion________________ for box in boxes: self.divide(box, depth - 1) return boxes #_______________________________________________________ # Uses the indices in the holeLUT to remove specific cubes # from the list of 27 cubes in the "boxes" arg. def delete(self, boxes): for n in range(len(self.holes)): hole = boxes.pop(self.holes[n]) self.deleted.append(hole) return boxes #======================================================= if __name__=="__main__": bounds = [-1,0,-1, 1,2,1] menger = Menger3D([], bounds, 2) print menger.retained ```

 ``` -------------------------------------------------Super Class--------------------------------------------------- --------------------------------------------------------------------------------------------------------------- from menger3D import Menger3D import ri_utils class Menger3DRib(Menger3D): def __init__(self, holes, bbox, num, ribpath): Menger3D.__init__(self, holes, bbox, num) self.path = ribpath self.bbox = bbox def write(self): f = open(self.path,'w') f.write('#bbox: %1.3f %1.3f %1.3f %1.3f %1.3f %1.3f\n' % (self.bbox[0], self.bbox[1], self.bbox[2], self.bbox[3], self.bbox[4], self.bbox[5]) ) readArc = '/home/micgao20/mount/stuhome/maya/projects/Menger/scenes/lego.rib' for box in self.retained: #for box in self.deleted: f.write(ri_utils.Cube(box)) f.close() ```

 ``` -------------------------------------------------ri_utils------------------------------------------------------ --------------------------------------------------------------------------------------------------------------- #_______________________________________________________ def Cube(bbox): pnts = [] minX,minY,minZ,maxX,maxY,maxZ = bbox rib = '\tPointsGeneralPolygons [1 1 1 1 1 1] ' rib += '[4 4 4 4 4 4]\n' rib += '\t\t[0 1 3 2 2 3 5 4 4 5 7 6 6 7 1 0 1 7 5 3 6 0 2 4]\n' rib += '\t\t"P" [' pnts.append('%1.3f %1.3f %1.3f' % (minX,minY,maxZ)) pnts.append(' %1.3f %1.3f %1.3f' % (maxX,minY,maxZ)) pnts.append(' %1.3f %1.3f %1.3f' % (minX,maxY,maxZ)) pnts.append(' %1.3f %1.3f %1.3f' % (maxX,maxY,maxZ)) pnts.append(' %1.3f %1.3f %1.3f' % (minX,maxY,minZ)) pnts.append(' %1.3f %1.3f %1.3f' % (maxX,maxY,minZ)) pnts.append(' %1.3f %1.3f %1.3f' % (minX,minY,minZ)) pnts.append(' %1.3f %1.3f %1.3f' % (maxX,minY,minZ)) rib += ''.join(pnts) rib += ']\n' return rib #_______________________________________________________ def __cube_edges(bbox): x0,y0,z0,x1,y1,z1 = bbox edges = [] # lower edges edges.append([ [x0,y0,z0], [x0,y0,z1] ]) # edge 0_1 edges.append([ [x0,y0,z1], [x1,y0,z1] ]) # edge 1_2 edges.append([ [x1,y0,z1], [x1,y0,z0] ]) # edge 2_3 edges.append([ [x1,y0,z0], [x0,y0,z0] ]) # edge 3_0 # upper edges edges.append([ [x0,y1,z0], [x0,y1,z1] ]) # edge 4_5 edges.append([ [x0,y1,z1], [x1,y1,z1] ]) # edge 5_6 edges.append([ [x1,y1,z1], [x1,y1,z0] ]) # edge 6_7 edges.append([ [x1,y1,z0], [x0,y1,z0] ]) # edge 7_4 # vertical edges edges.append([ [x0,y0,z0], [x0,y1,z0] ]) # edge 0_4 edges.append([ [x0,y0,z1], [x0,y1,z1] ]) # edge 1_5 edges.append([ [x1,y0,z1], [x1,y1,z1] ]) # edge 2_6 edges.append([ [x1,y0,z0], [x1,y1,z0] ]) # edge 3_7 return edges def CubeEdges(bbox, width): edges = __cube_edges(bbox) rib = '' for edge in edges: pnts = [] begin,end = edge x,y,z = begin X,Y,Z = end rib += '\tCurves "linear" [2] "nonperiodic" \n' rib += '\t\t"P" [' pnts.append('%1.3f %1.3f %1.3f' % (x,y,z)) pnts.append(' %1.3f %1.3f %1.3f' % (X,Y,Z)) rib += ''.join(pnts) rib += '] "constantwidth" [%1.3f]\n' % width return rib #_______________________________________________________ def Polygon(verts): pnts = [] rib = 'Polygon "P" [' for vert in verts: x,y,z = vert pnts.append('%1.3f %1.3f %1.3f ' % (x,y,z)) rib += ''.join(pnts) rib += ']\n' return rib #_______________________________________________________ def HierarchicalSubdivisionMesh(mesh): loops = [] # a list of the number of vertices of each face vertLUT = {} # a sequence of unique vertices for poly in mesh: loops.append(' %d' % len(poly)) for vert in poly: vertLUT[vert] = vert # Convert the LUT to a list so that indexing can be used pnts = [] for item in vertLUT.keys(): pnts.append(item) indices = [] for poly in mesh: for vert in poly: indices.append(' %d' % pnts.index(vert)) rib = 'HierarchicalSubdivisionMesh "catmull-clark" \n[' rib += ''.join(loops) rib += ']\n[' rib += ''.join(indices) rib += ']\n' rib += '["creasemethod" "facevaryingpropagatecorners" "interpolateboundary"] [0 0 1 1 0 0 1 0 0] [1 1] [] ["chaikin"]\n' rib += '"P" [\n' pntstr = [] for pnt in pnts: x,y,z = pnt pntstr.append('%1.4f %1.4f %1.4f ' % (x,y,z)) rib += ''.join(pntstr) rib += ']\n' return rib #_______________________________________________________ def PointsGeneralPolygons(mesh): faces = [] loops = [] vertLUT = {} for poly in mesh: faces.append(' 1') loops.append(' %d' % len(poly)) for vert in poly: vertLUT[vert] = vert pnts = [] for item in vertLUT.keys(): pnts.append(item) pnts.sort() indices = [] for poly in mesh: for vert in poly: indices.append(' %d' % pnts.index(vert)) rib = 'PointsGeneralPolygons [' rib += ''.join(faces) rib += ']\n[' rib += ''.join(loops) rib += ']\n[' rib += ''.join(indices) rib += '] \n"P" [\n' pntstr = [] for pnt in pnts: x,y,z = pnt pntstr.append('%1.4f %1.4f %1.4f ' % (x,y,z)) rib += ''.join(pntstr) rib += ']\n' return rib #_______________________________________________________ def Cylinder(bbox): x,y,z,X,Y,Z = bbox xrad = (X - x) / 2 zrad = (Z - z) / 2 rad = (xrad + zrad) / 2 height = Y - y rib = 'TransformBegin\n' rib += 'Translate %1.3f %1.3f %1.3f \n' % (x,y,z) rib += 'Rotate 90 1 0 0\n' rib += 'Cylinder %1.3f 0 %1.3f 360\n' % (rad,height) rib += 'TransformEnd\n' return rib #________________________________________________________ def RibArc(bbox, ribArcPath): x,y,z,X,Y,Z = bbox w = (X - x)/2 h = (Y - y)/2 ribFile = ribArcPath rib = 'TransformBegin\n' rib += 'Translate %1.3f %1.3f %1.3f \n' % (x,y,z) rib += 'Scale %1.3f %1.3f %1.3f\n' %(w, h, w) rib += 'ReadArchive "%s"\n' % (ribFile) rib += 'TransformEnd\n' return rib ```

### Menger 2D

Render of the basic 2D Menger Sponge.

Above are renders of the 2D Menger Sponge with using Rib Archives.

 ``` -------------------------Ripples-------------------------- ---------------------------------------------------------- # Open the file for writing f = open(self.path, 'w') for rect in self.data: x,y,z,X,Y,Z = rect w = X - x h = Z - z resultx = math.sin((x+X) * 4) * .1 resultz = math.cos((z+Z) * 2) * .1 f.write('polyPlane -w %1.3f -h %1.3f -sx 1 -sy 1;\n' % (w,h)) y = resultx + resultz f.write('polyPlane -w %1.3f -h %1.3f -sx 1 -sy 1;\n' % (w,h)) f.write('move %1.3f %1.3f %1.3f;\n' % (x, y ,z)) # Close the file. f.close() ```

 ``` ------------------------Randomness------------------------ ---------------------------------------------------------- # Open the file for writing f = open(self.path, 'w') for rect in self.data: x,y,z,X,Y,Z = rect w = X - x h = Z - z randRot = random.choice(deg) f.write('polyPlane -w %1.3f -h %1.3f -sx 1 -sy 1;\n' % (w,h)) f.write('polyPlane -w %1.3f -h %1.3f -sx 1 -sy 1;\n' % (w,h)) f.write('rotate -r %s %s %s;\n' % (randRot, randRot, randRot)) f.write('move %1.3f %1.3f %1.3f;\n' % (x, y ,z)) # Close the file. f.close() ```

 ``` ---------------------------Dip---------------------------- ---------------------------------------------------------- # Open the file for writing f = open(self.path, 'w') for rect in self.data: x,y,z,X,Y,Z = rect w = X - x h = Z - z heightY = math.sqrt(math.pow(x,2) + math.pow(z,2))*.75 f.write('polyPlane -w %1.3f -h %1.3f -sx 1 -sy 1;\n' % (w,h)) f.write('polyPlane -w %1.3f -h %1.3f -sx 1 -sy 1;\n' % (w,h)) f.write('move %1.3f %1.3f %1.3f;\n' % (x, heightY,z)) # Close the file. f.close() ```

 ``` ---------------------------Wave--------------------------- ---------------------------------------------------------- # Open the file for writing f = open(self.path, 'w') for rect in self.data: x,y,z,X,Y,Z = rect w = X - x h = Z - z resultx = math.sin((x+X) * 2) * .5 resultz = math.cos((z+Z) * .5)+ (x * 6) * .2 f.write('polyPlane -w %1.3f -h %1.3f -sx 1 -sy 1;\n' % (w,h)) y = resultx + resultz f.write('move %1.3f %1.3f %1.3f;\n' % (x, y,z)) # Close the file. f.close() ```

###### Menger 2D Codes

 ``` --------------------------------------------------Base Class--------------------------------------------------- --------------------------------------------------------------------------------------------------------------- class Menger2D: def __init__(self, rect, depth): self.bbox = rect self.data = [] self.divide(rect,depth) #_______________________________________________________ # A recursive proc that subdivides a rectangle into # 9 sub-rects. Each time the proc is called the arg # "depth" is decremented. Recursion terminates when its # value becomes zero. def divide(self, rect, depth): if depth == 0: self.data.append(rect) return x0,y0,z0, x1,y1,z1 = rect w = float(x1 - x0)/3 h = float(y1 - y0)/3 d = float(z1 - z0)/3 x,y,z = x0,y0,z0 rects = [] x = x0 for rows in range(3): rects.extend(self.column(x,y,z,w,h,d)) x = x + w rects = self.delete(rects) # Recursion________________ for rect in rects: self.divide(rect, depth - 1) return #_______________________________________________________ def delete(self, rects): hole = rects.pop(10) return rects #_______________________________________________________ # Given the minimum x,z and maximum x,z coordinates # of a rectangle this proc returns the coordinates # of a "row" of three sub-rectangles. def column(self, x,y,z, w,h,d): #x,y,z = x0,y0,z0 X,Y,Z = x + w, y + h, z + d rects = [] for n in range(3): rect = [x,y,z, X,Y,Z] rects.append(rect) z,Z = z + d, Z + d return rects if __name__ == '__main__': bounds = [-1,0,-1, 1,0,1] menger = Menger2D(bounds,3) print('number of sub-rects is %d' % len(menger.data)) for d in menger.data: print('%1.3f %1.3f %1.3f' % (d[0], d[1], d[2])) ```

 ``` -------------------------------------------------Super Class--------------------------------------------------- --------------------------------------------------------------------------------------------------------------- # menger2d_mel.py from menger2d import Menger2D import random, math, collections class RibMenger2D(Menger2D): # Our constructor def __init__(self,rect,depth,path): # Base class constructor Menger2D.__init__(self,rect,depth) self.path = path self.write() def write(self): """This method writes the data generated by the base class as a mel script containing lots of polyPlane mel commands.""" # Open the file for writing f = open(self.path, 'w') f.write('#bbox: 1 0.5 1 -1 -0.5 -1\n') for rect in self.data: x,y,z,X,Y,Z = rect w = X - x h = Z - z f.write ('TransformBegin \n') f.write('Translate %1.3f 0 %1.3f \n' % (x, z)) f.write('Polygon "P" [0 0 0 0 0 %1.3f %1.3f 0 %1.3f %1.3f 0 0]\n' % (h,w,h,w)) f.write ('TransformEnd \n') # Close the file. f.close() if __name__ == '__main__': rib1 = RibMenger2D( [-1, 0, -1, 1, 0, 1], 3, '/home/micgao20/mount/stuhome/tech312/rib/menger2D.rib') rib1.write() ```