L-Systems

Lindenmayer or L-systems provide a very simple way of coding/drawing complex structures. For those who may be interested the team thought we should demonstrate this simplicity by showing the code used in the patterns available in pzl_jigsaw.

Contents

As implemented for the simple patterns used in pzl_jigsaw we have a few commands defined in the dictionary self.commands and a loop to expand the productions for the given number of iterations.


class Lindenmayer(object):
    """
    Lindenmayer or L-Systems
    We've just implemented a few simple examples using a limited
    set of commands.
    """
    def __init__(self, axiom, productions, iterations):
        self.commands = {
            'Draw': set(['F','G','1']),
            'Move': 'f',
            'Turn left': '+',
            'Turn right': '-',
            'Push': '[',
            'Pop': ']'}


        current = axiom
        next_bit = ""

        for i in range(iterations):
            for symbol in range(len(current)):
                found_it = False
                for letter, production in productions.iteritems():
                    if (current[symbol] == letter):
                        next_bit = next_bit + production
                        found_it = True
                        break
                if not found_it:
                    next_bit = next_bit + current[symbol]
            current = next_bit
            next_bit = ""

        self.l_system = current


We have a little animal that can follow simple commands and leave behind its trail which will constitute the displayed drawing. With their customary modesty and wit the team have named this class "Hare".


class Hare(object):
    """
    Turtle graphics!
    current position is position
    current direction is angle
    current increment is step
    angle increment is delta_angle
    """
    def __init__(self, commands, step, delta):
        self.commands = commands
        self.stack = []
        delta = math.radians(delta)
        self.delta_angle = delta
        self.position = complex(0, 0j)
        self.angle = 270.0
        self.angle = math.radians(self.angle)
        self.step = complex(math.cos(self.angle), 
                            math.sin(self.angle))
        self.path = []
        self.x_min = self.position.real
        self.x_max = self.position.real
        self.y_min = self.position.imag
        self.y_max = self.position.imag


    def get_path(self, l_system):
        self.stack = []
        segment = []
        self.path = []
        for i in range(len(l_system)):
            if l_system[i] in self.commands['Draw']:
                segment.append(self.position)
                self.position += self.step
                segment.append(self.position)
                self.path.append(segment)
                segment = []
            elif l_system[i] == self.commands['Move']:
                self.position += self.step
            elif l_system[i] == self.commands['Turn left']:
                self.angle += self.delta_angle
                self.step = complex(math.cos(self.angle), 
                                    math.sin(self.angle))
            elif l_system[i] == self.commands['Turn right']:
                self.angle -= self.delta_angle
                self.step = complex(math.cos(self.angle), 
                                    math.sin(self.angle))
            elif l_system[i] == self.commands['Push']:
                item = (self.position, self.angle)
                self.stack.append(item)
            elif l_system[i] == self.commands['Pop']:
                item = self.stack.pop()
                self.position = item[0]
                self.angle = item[1]
                self.step = complex(math.cos(self.angle), 
                                    math.sin(self.angle))
            else:
                # unrecognised symbol so do nothing
                pass

        # get range

        for segment in self.path:
            self.x_min = min(self.x_min, segment[0].real, segment[1].real)
            self.x_max = max(self.x_max, segment[0].real, segment[1].real)
            self.y_min = min(self.y_min, segment[0].imag, segment[1].imag)
            self.y_max = max(self.y_max, segment[0].imag, segment[1].imag)

And finally the actual axioms and productions which, when processed as above, create the patterns seen on the screen. Where they have been taken from The Algorithmic Beauty of Plants the page numbers are shown.


    def start_game_l(self, name):
        """
        Define the codes for various l-systems.
        Mostly taken from Prusinkiewicz and Lindenmayer (page nos)
        """

        iterations = None
        if name == 'Koch a':
        #p10 a
            iterations = 4
            delta = 90
            axiom = "F-F-F-F"
            productions = {'F': 'FF-F-F-F-F-F+F'}
            side_ratio = 1.0
        elif name == 'Koch b':
        #p10 b
            delta = 90
            iterations = 4
            axiom = "F-F-F-F"
            productions = {'F': 'FF-F-F-F-FF'}
            side_ratio = 1.0
        elif name == 'Koch c':
        #p10 c
            delta = 90
            iterations = 3
            axiom = "F-F-F-F"
            productions = {'F': 'FF-F+F-F-FF'}
            side_ratio = 1.0
        elif name == 'Koch d':
        #p10 d
            delta = 90
            iterations = 4
            axiom = "F-F-F-F"
            productions = {'F': 'FF-F--F-F'}
            side_ratio = 1.0
        elif name == 'Koch e':
        #p10 e
            delta = 90
            iterations = 5
            axiom = "F-F-F-F"
            productions = {'F': 'F-FF--F-F'}
            side_ratio = 1.0
        elif name == 'Koch f':
        #p10 f
            delta = 90
            iterations = 4
            axiom = "F-F-F-F"
            productions = {'F': 'F-F+F-F-F'}
            side_ratio = 1.0
        elif name == 'Islands and Lakes':
        #p9, 1.8
            iterations = 2
            delta = 90
            axiom = "F+F+F+F"
            productions = {'F': 'F+f-FF+F+FF+Ff+FF-f+FF-F-FF-Ff-FFF',
                           'f': 'ffffff'}
            side_ratio = 1.0
        elif name == 'Quadratic Koch island':
            #p9 a
            iterations = 2
            delta = 90
            axiom = 'F-F-F-F'
            productions = {'F' : 'F+FF-FF-F-F+F+FF-F-F+F+FF+FF-F'}
            side_ratio = 1.0
        elif name == 'Quadratic snowflake':
        #p9, 1.7b
            iterations = 4
            delta = 90
            axiom = "-F"
            productions = {'F': 'F+F-F-F+F'}
            side_ratio = 1.0
        elif name == 'Dragon':
        #p11 a
            iterations = 10
            delta = 90
            axiom = "F"
            productions = {'F': 'F+G+', 'G' : '-F-G'}
            side_ratio = 1.0
        elif name == 'Sierpinski gasket':
        #p11 b
            iterations = 8
            delta = 60
            axiom = "G"
            productions = {'F': 'G+F+G', 'G' : 'F-G-F'}

            side_ratio = 1.0
        elif name == 'Plant 1':
        #p25 a
            iterations = 5
            delta = 27.7
            axiom = 'F'
            productions = {'F': 'F[+F]F[-F]F'}
            side_ratio = 0.4
        elif name == 'Plant 2':
        #p25 b
            iterations = 5
            delta = 20
            axiom = 'F'
            productions = {'F': 'F[+F]F[-F][F]'}
            side_ratio = 0.6
        elif name == 'Plant 3':
        #p25 c
            iterations = 4
            delta = 22.5
            axiom = 'F'
            productions = {'F': 'FF-[-F+F+F]+[+F-F-F]'}
            side_ratio = 0.7
        elif name == 'Plant 4':
        #p25 d
            iterations = 6
            delta = 20
            axiom = 'X'
            productions = {'X': 'F[+X]F[-X]+X',
                           'F': 'FF'}
            side_ratio = 0.7
        elif name == 'Plant 5':
        #p25 e
            iterations = 7
            delta = 25.7
            axiom = 'X'
            productions = {'X': 'F[+X][-X]FX', 'F': 'FF'}
            side_ratio = 0.5
        elif name == 'Plant 6':
        #p25 f
            iterations = 5
            delta = 22.5
            axiom = 'X'
            productions = {'X': 'F-[[X]+X]+F[+FX]-X',
                           'F': 'FF'}
            side_ratio = 0.7

        elif name == 'Sierpinski carpet':
            iterations = 4
            delta = 90.0
            axiom = 'F'
            productions = {'F': 'F+F-F-F-f+F+F+F-F',
                           'f': 'fff'}
            side_ratio = 1.0

        elif name == 'Penrose':
            iterations = 5
            delta = 36.0
            axiom = '[7]++[7]++[7]++[7]++[7]'
            productions = {'6': '81++91----71[-81----61]++',
                           '7': '+81--91[---61--71]+',
                           '8': '-61++71[+++81++91]-',
                           '9': '--81++++61[+91++++71]--71',
                           '1': ''}
            side_ratio = 1.0

        if iterations != None:
            self.do_l_systems(iterations, delta, axiom, productions, side_ratio)

Examples

Below we show videos of various examples of L-Systems patterns being generated by jigsaw.

Plant 1

A video from jigsaw showing the generation of an L-System plant 1 using the "Type" style.

Plant 2

A video from jigsaw showing the generation of an L-System plant 2 using the "Type" style.

Plant 3

A video from jigsaw showing the generation of an L-System plant 3 using the "Type" style.

Koch_a

A video from jigsaw showing the generation of an L-System Koch a using the "Type" style.

Koch_b

A video from jigsaw showing the generation of an L-System Koch b using the "Type" style.

Islands

A video from jigsaw showing the generation of an L-System Islands and Lakes using the "Type" style.

Penrose

A video from jigsaw showing the generation of an L-System Penrose using the "Random" style.