Welcome, guest | Sign In | My Account | Store | Cart
#!/usr/bin/env python
# coding: UTF-8
#
## @package _16_sierpinski
#
# Draws a Sierpinski Gasket.
#
# Usage: _16_sierpinski [number_of_divisions]
#
# @author Paulo Roma Cavalcanti 
# @since 09/02/2016
# @see http://paulbourke.net/fractals/gasket/
# @see https://en.wikipedia.org/wiki/Sierpinski_triangle
# @see http://ecademy.agnesscott.edu/~lriddle/ifs/siertri/siertri.htm
# @see http://www.oftenpaper.net/sierpinski.htm

import sys
try:
  from tkinter import *  # python 3
except ImportError:
  from Tkinter import *  # python 2

##  Creates a Sierpinski Gasket, by recursively partitioning
 #  an initial triangle (a,b,c) into three or four new triangles.
 #
 #  - There will be:
 #    - @f$3^{count}@f$ red triangles or 
 #    - @f$4^{count}@f$ if the fourth triangle is drawn.
 #
 #  - The number of white triangles is a Geometric Progression, starting at 1 and with ratio 3, given by:
 #     - P(0) = 0
 #     - P(n) = 3 * P(n-1) + 1
 #     - P(n) = @f$\frac{(3^n-1)}{2}.@f$
 #
 #  @param a first vertex coordinates.
 #  @param b second vertex coordinates.
 #  @param c third vertex coordinates.
 #  @param n number of subdivisions on each edge (depth of recursion).
 #  @param fourth whether to add the fourth triangle.
 #
def Sierpinski(a, b, c, n, fourth=False):
    ## list of points (a set of triangles).
    points = []

    ## Pushes three vertices to the list of points.
    def triangle( a, b, c ):
        points.append ( a )
        points.append ( b )
        points.append ( c )

    ## Returns a point on segment p1-p2 corresponding to parameter s.
    def mix(p1,p2,s):
        return [(p1[0]+p2[0])*s, (p1[1]+p2[1])*s]

    ## Divides triangle (a,b,c), by recursively subdividing
     # each edge at its middle point, and connecting the new points.
    def divideTriangle( a, b, c, count ):
        # check for end of recursion
        if ( count == 0 ):
            triangle( a, b, c )
        else:
            # bisect the sides
            ab = mix( a, b, 0.5 )
            ac = mix( a, c, 0.5 )
            bc = mix( b, c, 0.5 )

            count -= 1

            # three new triangles
            divideTriangle( a, ab, ac, count )
            divideTriangle( c, ac, bc, count )
            divideTriangle( b, bc, ab, count )

            # add the middle triangle
            if ( fourth ): divideTriangle( ac, bc, ab, count )

    divideTriangle (a, b, c, n)
    return points

## Draw triangles, given in points, on canvas c.
def draw(c, points, contour=False):
    w = c.winfo_width()//2
    h = c.winfo_height()//2
    centerx = w
    centery = h
    c.delete(ALL)
    for i in range(0,len(points),3):
        if ( not contour ):
            c.create_polygon(centerx+w*points[i]  [0],centery-h*points[i  ][1], 
                             centerx+w*points[i+1][0],centery-h*points[i+1][1], 
                             centerx+w*points[i+2][0],centery-h*points[i+2][1], fill='red', outline='black')
        else: 
            c.create_line(centerx+w*points[i]  [0],centery-h*points[i  ][1], 
                          centerx+w*points[i+1][0],centery-h*points[i+1][1], 
                          centerx+w*points[i+2][0],centery-h*points[i+2][1],
                          centerx+w*points[i]  [0],centery-h*points[i  ][1], fill='black', width=2)

## Main program.
def main(argv=None):

    ## Resize the graphics when the window changes.
    def resize(event=None):
        global pts
        draw(canvas, pts, cntVar.get()=='ON')

    ## Redraw the graphics when the scale changes
    def redraw(event=None):
        global pts

        # Initialize the corners of the gasket with three points.
        vertices = [
            [ -1, -1 ],
            [  0,  1 ],
            [  1, -1 ]
        ]
        pg = lambda n: (3**n-1)//2 
        ndiv = slider.get()
        pts = Sierpinski( vertices[0], vertices[1], vertices[2], ndiv, fillVar.get()=='ON' )
        lr.configure(text="Red Triangles = %d     "%(len(pts)//3))
        la.configure(text="   White Triangles = %d"%(pg(ndiv)))
        resize(event)

    ndiv = 2

    if argv is None:
       argv = sys.argv
    if len(argv) > 1:
       try:
          ndiv = abs(int(argv[1]))
       except:
          ndiv = 3 

    root = Tk()
    root.title("Sierpinski Gasket")
    bot = Frame(root)
    bot.pack(side=BOTTOM)
    top = Frame(root)
    top.pack(side=TOP)
    canvas = Canvas(root,width=400,height=400);
    canvas.bind("<Configure>", resize)
    canvas.pack(expand=YES,fill=BOTH)

    slider = Scale(bot, from_=0, to=8, command=redraw, orient=HORIZONTAL)
    slider.set(ndiv)
    slider.pack(side=LEFT, anchor = W)

    cntVar = StringVar()  # create a checkbutton for the drawing style
    cntVar.set ( "OFF" )
    c = Checkbutton (bot, text="Contour", variable=cntVar, onvalue="ON", offvalue="OFF", command=resize)
    c.pack(side=BOTTOM)

    fillVar = StringVar()  # create another checkbutton for the number of triangles
    fillVar.set ( "OFF" )
    c = Checkbutton (bot, text="Fill", variable=fillVar, onvalue="ON", offvalue="OFF", command=redraw)
    c.pack(side=LEFT)

    lr = Label(top, text="")
    lr.pack(side=LEFT,anchor = W)
    la = Label(top, text="")
    la.pack(side=RIGHT,anchor = E)

    redraw(None)

    mainloop()

if __name__=='__main__':
    sys.exit(main())

History