Initializing 3D Canvas...

L Systems

2 min read1 page

In 1968, a Hungarian biologist named Aristid Lindenmayer wanted to understand how plants grow. He realized that plant growth could be modeled using simple string replacements, creating what is now known as an L-System.

The Axiom and the Rules:

1. We start with a single letter, called the Axiom. (e.g., "F") 2. We define a Rule that says how to replace that letter. (e.g., "F" -> "F+F-F-F+F") 3. The letters aren't just text; they are commands for a robot (a "Turtle") to draw a shape! 4. F means "Draw a line forward". + means "Turn Right". - means "Turn Left".
python
1# Defining the Rules of Nature
2def define_l_system_grammar():
3 """Setting up the alphabet and the rules."""
4
5 # The starting point (Axiom)
6 axiom = "F"
7
8 # The Rules: Every time you see 'F', replace it with this string!
9 # F = Draw Forward
10 # + = Turn Right
11 # - = Turn Left
12 rules = {
13 "F": "F+F-F-F+F"
14 }
15
16 return axiom, rules
View (Axiom / Rule Result)
0.00
2 min read1 page

The true power of an L-System comes from Recursion. We don't just apply the rule once; we take the resulting text string, and feed it back into the rule engine again!

Exponential Growth:

1. Gen 0: F2. Gen 1: F+F-F-F+F3. Gen 2: Every single F in Gen 1 is replaced by the entire rule again! The string explodes into F+F-F-F+F+F+F-F-F+F-F+F-F-F+F-F+F-F-F+F+F+F-F-F+F. 4. Within 4 or 5 generations, a simple 5-character rule produces a string that is tens of thousands of characters long. This perfectly mimics how plant cells divide and multiply.
python
1# Replacing Text with Text
2def expand_grammar(current_string, rules):
3 """The recursive heart of the algorithm."""
4
5 next_string = ""
6
7 # Read every character one by one
8 for char in current_string:
9
10 # If there's a rule for this letter, swap it!
11 if char in rules:
12 next_string += rules[char]
13
14 # Otherwise, just keep the letter as it is
15 else:
16 next_string += char
17
18 return next_string
Generation
0.00
2 min read1 page

In 2D, a turtle only needs to know its X/Y position and a single Angle (e.g., facing 90 degrees North). But to generate a 3D tree, the turtle must be able to pitch up/down and roll left/right like an airplane.

Local Coordinate Systems:

1. To handle 3D rotation, the turtle must track three distinct vectors at all times: Heading (Forward), Left, and Up. 2. When the command + (Yaw Right) is read, the turtle rotates its Heading and Left vectors around its Up vector using a mathematical Rotation Matrix. 3. The grammar alphabet is expanded to include Pitch (& and ^) and Roll (\ and /). 4. By combining these commands, the turtle can twist and bend organically through 3D space.
python
1# Navigating in 3D Space
2def rotate_turtle(current_direction, axis, angle):
3 """The Matrix handles the hard math for us."""
4
5 # + = Turn around Z axis (Yaw)
6 # & = Pitch down around X axis
7 # ^ = Pitch up around X axis
8 # \ = Roll left around Y axis
9 # / = Roll right around Y axis
10
11 # We use a 3x3 Rotation Matrix to perfectly calculate the new 3D vector
12 rotation_matrix = Matrix.Rotation(angle, axis)
13
14 new_direction = rotation_matrix * current_direction
15
16 return new_direction
Rotation (Yaw / Pitch / Roll)
0.00
2 min read1 page

A single line of text can only draw a single, continuous, squiggly line. But real trees have branches that split off and stop, while the main trunk keeps growing. How do we tell the Turtle to draw a branch and then come back?

Push and Pop:

1. We introduce two magical brackets: [ and ]. 2. When the Turtle reads [, it saves its exact current Position and Rotation onto a memory Stack. 3. The Turtle continues drawing the sub-branch normally. 4. When the Turtle reads ], it stops drawing, grabs the saved memory from the Stack, and instantly teleports back to the main trunk! 5. Because it is a Stack (Last-In, First-Out), branches can have sub-branches, which can have sub-sub-branches indefinitely.
python
1# Creating Branches
2def process_branching_string(char, turtle, stack):
3 """How to grow a leaf and return to the main trunk."""
4
5 # '[' pushes the current State onto the Stack
6 if char == "[":
7 current_state = {
8 "position": turtle.position.copy(),
9 "heading": turtle.heading.copy()
10 }
11 stack.push(current_state)
12
13 # ']' pops the State off the Stack
14 elif char == "]":
15 saved_state = stack.pop()
16
17 # Teleport the turtle back to where it was!
18 turtle.position = saved_state["position"]
19 turtle.heading = saved_state["heading"]
Sequence (Trunk / Branch / Return)
0.00
2 min read1 page

Up until now, the Turtle has just been drawing mathematical 1D lines in empty space. But real trees have volume. To generate a 3D model we can render or 3D print, we must convert those 1D lines into solid 3D geometry.

Meshing the Skeleton:

1. For every single line segment the Turtle draws, we generate a 3D Cylinder. 2. We set the height of the cylinder to exactly match the length of the line. 3. We mathematically calculate a quaternion (3D rotation) to align the cylinder perfectly with the direction of the line. 4. Finally, to make the joints look organic and connected, we place a smooth 3D Sphere exactly at every intersection node.
python
1# Generating Real 3D Geometry
2def build_3d_branch(start_point, end_point, thickness):
3 """Converting invisible math lines into solid wood."""
4
5 # 1. The Turtle drew a line from start to end
6 vector = end_point - start_point
7 length = length(vector)
8
9 # 2. We generate a 3D Cylinder geometry
10 branch_mesh = Cylinder(radius=thickness, height=length)
11
12 # 3. We move the cylinder to exactly halfway between the points
13 midpoint = (start_point + end_point) / 2
14 branch_mesh.position = midpoint
15
16 # 4. We rotate the cylinder so it aligns perfectly with the vector
17 branch_mesh.align_to_vector(vector)
18
19 return branch_mesh
Branch Thickness
2.00
2 min read1 page

If every branch we drew was the exact same length and thickness, our tree would look like a bunch of identical PVC pipes glued together. Nature doesn't work like that. As trees branch out, they taper.

Decay Variables:

1. We add two new variables to our Turtle: Current Thickness and Current Length. 2. Every time the Turtle draws a branch (F), we multiply those variables by a decay factor (e.g., 0.85). 3. Because this happens before we push the state to the Stack ([), the sub-branches inherit the smaller size! 4. This guarantees that the main trunk is massive, the branches are medium, and the outer twigs are incredibly thin and short.
python
1# Natural Tapering
2def build_tree_branch(char, turtle):
3 """Making it look organic."""
4
5 # 1. As the turtle moves forward, it draws a branch
6 if char == "F":
7
8 # Draw the cylinder using the CURRENT thickness
9 build_3d_branch(turtle.position, thickness=turtle.current_thickness)
10
11 # 2. Every time a branch grows, it gets slightly thinner!
12 # This creates the natural tapering of a real tree trunk.
13 turtle.current_thickness = turtle.current_thickness * 0.85
14
15 # 3. The branch also gets slightly shorter!
16 turtle.current_length = turtle.current_length * 0.90
Decay Scaling (1.0 -> 0.5)
5.00
2 min read1 page

Spawning thousands of 3D cylinder meshes for dense fractal trees is highly expensive. Using a 2D HTML5 Canvas, we can expand our grammar up to 6 generations (generating over 15,000 branch segments) and draw them instantly at 60 FPS.

Botanical Mechanics:

1. Grammar Expansion: Recursively rewrite the string according to L-system rules. 2. Turtle Drawing State: The turtle processes the string commands sequentially, drawing branches or branching off using the stack coordinates.
python
1# L-System parser drawing to a 2D canvas
2def draw_l_system(canvas_ctx, expanded_string, angle_rad, step_size):
3 stack = []
4 x, y = 0.0, 0.0
5 heading = -Math.PI / 2 # Upwards
6
7 for cmd in expanded_string:
8 if cmd == 'F':
9 nx = x + cos(heading) * step_size
10 ny = y + sin(heading) * step_size
11 canvas_ctx.line(x, y, nx, ny)
12 x, y = nx, ny
13 elif cmd == '+':
14 heading += angle_rad
15 elif cmd == '-':
16 heading -= angle_rad
17 elif cmd == '[':
18 stack.append((x, y, heading))
19 elif cmd == ']':
20 x, y, heading = stack.pop()
Generations (Plant Depth)
4.00
Branch Turn Angle (Degrees)
25.00
Organic Green Coloring