Your Image

L-SYSTEM TREES

Introduction & Inspiration

In computational science, we are often interested in simulating nature, capturing its effects in our computers. I believe that the beauty of nature can never be fully expressed through artificial means. But painters, sculptors, and poets have tried, and so shall we.
In this project, I implement an L-system tree generator, based on the work of the Hungarian biologist Aristid Lindenmayer who was interested in how algae can be modelled algorithmically. My implementation is probabilistic, meaning it will create a unique, randomly generated tree every time.

What is an L-system?

An L-system is a formal grammar consisting of symbols and a collection of rules. The rules define how the system evolves based on the symbols used. For example, we might have a system consisting of the symbols A and B, with rules that change A to B and B to A. So if we start with A, the next step gives us B, and then A, and so on. This is a very basic system, but it illustrates the rewriting aspect of a formal grammar.

The Steps

To illustrate the process, I will choose a random rule and apply it in the steps as an example.
1. Definitions: we begin by defining our symbols and rules. For example, I will choose the symbols A and B, and the rules which convert an A to BB, and B to BA.
2. Axiom: we start with an initial string of symbols. We often begin with an unused symbol, such as S, and rewrite it to a string using symbols from our alphabet, such as AA. This is effectively the same as starting with AA, but it is there for purposes of consistency.
3. Iteration: we iterate over the string until we are satisfied (or forever, if we are particularly hard to please). In our example, the process will look as follows: S -> AA -> BBBB -> BABABABA -> BABBBABBBABBBABB, and so on.
4. Interpretation: we intrepret what our symbols mean, usually in a visual way. For example, I might decide that A is a circle of diameter 1 and B is a square of side length 2, giving me some geometric picture. This particular example is not interesting, because it's been simplified to illustrate how L-systems work.

Examples

Let's take a look at some examples. I will consider the following symbols and interpretations: F and G for forward, meaning draw a straight line; + and - for rotate right and left.
Axiom: F
Rules: F -> F-F+F+F-F
Angle: 90 degrees

some-fractal

Axiom: F-G-G
Rules: F -> F-G+F+G-F and G -> GG
Angle: 120 degrees
This produces the famous Sierpinski triangle fractal.

the sierpinski triangle

The Implementation

Let's begin by defining a tree class. A tree class should contain a method that expands a given string, applying the rules to it to generate the next iteration, and a method that interprets the string to create a drawing out of it.

        
  class Tree {
    constructor(length, angle) {
      this.s = "S";
      this.length = length;
      this.angle = angle;
    }

    expand() {
      resultString = "";
      for (let i = 0; i < this.s.length; i++) {
        resultString += rule(this.s[i]);
      }
      this.s = resultString;
    }

    render() {
      for (let i = 0; i < this.s.length; i++) {
        strokeWeight(2);
        if (this.s[i] == "F") {
          line(0, 0, this.length, 0);
          translate(this.length, 0);
        } else if (this.s[i] == "-") {
          rotate(-this.angle);
        } else if (this.s[i] == "+") {
          rotate(this.angle);
        } else if (this.s[i] == "[") {
          push();
        } else if (this.s[i] == "]") {
          pop();
        }
      }
    }
  }

  function rule(c) {
    if (c == "S") {
      return "FB";
    } else if (c == "F") {
      return c;
    } else if (c == "B") {
      return "[-FBB][+FBB]";
    } else {
      return c;
    }
  }  
        
        

The symbols are interpreted as follows: F for forward, - for rotating left, + for rotating right, B is a placeholder for now, and [ and ] are used for grouping transformations together.
After a few frames, this generates the following tree.

simple tree

It is very simple and predictable. To generate a unique tree every time, we need to implement some randomness into the rule function.

        
  function rule(c) {
    if (c == "S") {
      return "FB";
    } else if (c == "F") {
      if (random(1) < 0.5) {
        return "FF";
      } else return c;
    } else if (c == "B") {
      let r = random(1);
      if (r < 0.1) {
        return "[-FBB][+FBB]";
      } else if (r < 0.5) {
        return "[-FBB][+FB]";
      } else if (r < 0.8) {
        return "[-FB][+FBB]";
      } else {
        return "[-BB][+BBFB]";
      }
    } else {
      return c;
    }
  }
        
        

This is the ruleset I came up with, and it is capable of generating trees like the one below. Each time the program is run, the generated tree is unique.

random tree

To make things more fun, I added leaves and made the branches decrease in thickness as we travel upwards, somewhat similar to real trees where the base of the trunk is thickest and the branches are thin.

colored tree

Please play around with the project or read the full code using the links above! The full version is interactive, allowing you to generate infinitely many possibilities with different colors and angles!