L-Systems

Implementing L-Systems in Processing with turtle graphics, probabilistic branching, and laser-etched fabrication.

L-Systems

L-System Implementation

Iteration

In the draw function, we tell the L-System class to iterate for the total number of iterations, and then draw the entire system:

//call iterate method for total number of iterations
for(int i = 0; i < numIterations; i++)
{
    lSys.iterate();
}

// Draw the LSystem using the turtle 
lSys.drawLSystem(t);

This means the entire L-System is redrawn with each iteration, rather than only drawing subsequent iterations.

Rule Evaluation

We loop through each character of the (previous) iteration buffer, and use that character as the key in our rules dictionary. If the key is valid, we append the dictionary value to the “new” buffer. If the key is not valid, we append the original character - which is how we can handle constants.

String current = this.getIterationString();
this.clearCurrentStringBuffer();

for (int i = 0; i < current.length(); i++) {
    
    Character c = current.charAt(i); 
    if(this.rules.get(c) != null)
    {
        this.currentIterationBuffer.append(this.rules.get(c));
    } else
    {
        this.currentIterationBuffer.append(c);
    }
}

Draw Vocabulary

In order to draw the L System, we again loop through each character in the current string buffer, and pass that character into a switch, where the turtle is given unique drawing instructions per character in the vocabulary.

I implemented the basic drawing functions (forward, angle left and right, push and pop) as well as some additional functions for creating curves and discrete shapes.

I also included blank utility characters (A, B, X, Y etc) which can be used in the rule set to set up branches without actively drawing.

public void drawLSystem(Turtle t) {
       
    // Our turtle's move distance
    float dist = this.moveDistance;
    float angle = this.rotateAngle;
    
    // Get the current iteration string
    String currentIteration = this.getIterationString(); 
    
    //do turtle operations based on the character
    for (int i = 0; i < currentIteration.length(); i++) {
      
      Character c = currentIteration.charAt(i);

      switch (c) {
        case 'F': // Forward
          t.forward(dist);
          break; 
        case 'f': // Forward without drawing
          t.penUp();
          t.forward(dist);
          t.penDown();
          break;
        case '+': // Angle Right
          t.right(angle);
          break;
        case '-': // Angle Left
          t.left(angle);
          break;
        case '(': // Curve Right
          for(int z = 0; z < dist; z++)
          {
            t.right(angle/dist);
            t.forward(1);
          }
          break;
        case ')': // Curve Left
          for(int z = 0; z < dist; z++)
          {
            t.left(angle/dist);
            t.forward(1);
          }
          break;
        case 'T': //Triangle Leaf
            float l = dist;
            t.left(30);
            t.forward(l);
            t.right(120);
            t.forward(l);
            t.right(120);
            t.forward(l);
          break;
        case 'O': // Circle
            float resolution = 120;
            float circumference = 2 * (dist/2) * PI;
            
            t.left(90);
            
            for(int z = 0; z < resolution; z++)
            {
               t.right(360/resolution);
               t.forward(circumference / resolution);
            }
          break;
        case '[':
          t.push();
          break;
        case ']':
          t.pop();
          break;
        case 'X': // Branch
          break;
        case 'Y': // Branch
          break;

          ///etc.
    }

Probability and Randomness

There are a number of ways to inject randomness into the L-System: we can randomize variables like distance and angle each iteration, or we can have rules with multiple possible outcomes.

In order to implement multiple possible outcomes per rule, we can use a string array as the mapped value per each character, and then select one of the options at random:

for (int i = 0; i < current.length(); i++) {
    
    Character c = current.charAt(i);
    String[] rule = this.rules.get(c);

    if(rule != null)
    {
        int sel = rand.nextInt(rule.length);
        String selection = rule[sel];
        this.currentIterationBuffer.append(selection);
    }

We can also randomize angle and distance for each iteration of the draw function, in this case constrained by a range variable:

public float getRandomAngle()
{
    return rand.nextFloat(rotateAngle - angleRange/2, rotateAngle + angleRange/2);
}

public float getRandomDistance()
{
    return rand.nextFloat(moveDistance - distRange/2, moveDistance + distRange/2);
}

Attempting Determinism

Since the L-system is being drawn from scratch each draw(), using a true random value for each case would mean the entire system would be randomized each iteration, breaking continuity between iterations and preventing natural “growth” with probabilistic branching. So I attempted to use seeded random instead, and initialize a new Random class on each reset with a global seed. The global seed is randomized each time the application is run. This should mean plant-like L-systems will appear to grow naturally, even with random variables, but be different between runs.

However, branching L systems lead to the random calls being evaluated in different orders regardless, so the system loses continuity after the first branch anyway. Oh well!

Complete Code

App: https://gist.github.com/colinegge/e2e9882e95c01166b710b3c5b0472017

L-System: https://gist.github.com/colinegge/f345bdb8853dde89cafc44c989209b7f

Probabilistic L-System: https://gist.github.com/colinegge/a6c5a22d3bc48830c475c7005d3fa110

Sources

An overview of some L System techniques by Paul Bourke c. 1991: https://paulbourke.net/fractals/lsys/

L System Designs

Circuit Tree

Non-probabilistic L System

distance = 15
ø = 45

axiom = ARL

Rules Notes
L → [))B]R Curve left 90 degrees, B sprout, and continue right
R → [(A]L Curve right 90 degrees, B sprout, and continue left
A → F[(X]FFB Long branch with right X sprout
B → F[)Y]FFA Long branch with left Y sprout
Y → ((FFBFFO Y Sprout becomes straight branch with A recurrance and circle endcap
X → ))FFA((FO X Sprout becomes curvy branch with B recurrance and circle endcap
Outcome

Circuit Tree

Iteration String
0 ARL
1 F[(X]FFB[(A]L[))B]R
2 F[())FFA((FO]FFF[)Y]FFA[(F[(X]FFB][))B]R[))F[)Y]FFA][(A]L
3 F[())FFF[(X]FFB((FO]FFF[)((FFBFFO]FFF[(X]FFB[(F[())FFA((FO]FFF[)Y]FFA][))F[)Y]FFA][(A]L[))F[)((FFBFFO]FFF[(X]FFB][(F[(X]FFB][))B]R

Garden

An attempt at more intensional illustration - leading to some pretty hacky and prescriptive rules, but a nice outcome.

Probabalistic branching, fixed distance and angles.

distance = 10
ø = 30
angle range = 0;
distance range = 0;

axiom = GG

Rules Notes
G → +++())X(())(())X(())(())X(())X(())(— Ground tile, with X’s as “seeds”
X → “[–FfB]”,“X” Seed has 50% chance of sprouting
F → “YF”, “F”, “F” Stem has 1/3 change to branch and grow
Y → “F[-FL]”, “F[+FL]”, “F” Branch left or right or cancel
L → “T”,“L”, “L”, “L” Branch (FL) has 1/4 change to grown a leaf
B → “O”, “B”, “B” 1/3 chance to bud
O → “OO”, “O” Bud grows into a flower
Outcome

Garden

Variations
1 2 3 4
garden1 garden2 garden3 garden4
Iteration String
0 GG
1 +++())X(())(())X(())(())X(())X(())(—+++())X(())(())X(())(())X(())X(())(—
2 +++())X(())(())[–FfB](())(())[–FfB](())[–FfB](())(—+++())[–FfB](())(())X(())(())[–FfB](())[–FfB](())(—
3 +++())X(())(())[–FfB](())(())[–FfB](())[–FfB](())(—+++())[–FfO](())(())X(())(())[–YFfO](())[–YFfB](())(—

Trash Plant

Crawling Vine with randomized distance, angles, and branching

distance = 60
distance range = 120;
ø = 45
angle range = 90;

axiom = O

Rules Notes
O → “(O”, “)O”, “(O”, “)O”, “)YO”, “(YO” Grow with curve right or left, or start branch
Y → “[+(O]”, “[-(O]”, “Y”, “Y” 50% to sprout new branch with identical behavior
Output

Trash Plant

Variations
1 2 3 4
crawl1 crawl2 crawl3 crawl4
Iteration String
0 O
1 (O
2 ()O
3 ()(O
18 ()(([+())()))(()O])[-()(([+((YO]()[+(O])YO]()()([+())([-()(YO])[-())YO]))[+(O](YO]()))))[-(O])(YO

Code

L System Designs: https://gist.github.com/colinegge/cc9ed70f14d48a9a15264baeb8162314

Fabrication

The circuit tree and garden systems were laser etched into acrylic and wood.

Garden fabrication Circuit red acrylic Circuit wood