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()
	
	
			



Conclusion


After understanding of basic python syntax from the Sierpinski exercise, I felt much more comfortable with the class systems. I enjoyed this Menger Sponge project because I was able to implement what I had learned and then built upon that to make a rendered pictures of my own.