L-SYSTEM TREES
Project Overview and Links
This project is an interactive random tree generator. It uses
L-systems to model tree growth algorithmically.
To view the full project, click
here 🔗
You can choose style, colors, and branching angles, and click the
"new" button or hit space on your keyboard to generate a new tree
every time.
To view the full source code for this project, please click
here 🔗
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
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 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.
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.
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.
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!