Daniel Gray

Thoughts, Notes, Ideas, Projects

← Back to home

L-System 3D Tree Generation

L-systems (Lindenmayer systems) are a formal grammar system developed by biologist Aristid Lindenmayer in 1968 for modeling biological growth, particularly well-suited for plants and trees. This article covers the implementation of L-systems for generating 3D fractal trees in Three.js, used in the animated background system.

The interactive visualization above demonstrates various L-system configurations. You can:

  • Select predefined L-systems to see different tree structures

  • Enter custom axioms and rules to create your own tree patterns

  • Adjust iterations and angle to see how parameters affect the tree shape

  • Rotate and zoom by clicking and dragging or scrolling

Overview

L-systems are based on rewriting rules that replace symbols in a string iteratively. The generated string is then interpreted by a "turtle" (a virtual drawing agent) that moves and rotates in 3D space to draw the tree structure.

Key Concepts

An L-system consists of:

  • Axiom: The initial string (seed) - e.g., "F" or "X"

  • Rules: Production rules that replace symbols - e.g., F → F[+F]F[-F]F

  • Iterations: How many times to apply the rules (typically 3-6)

  • Angle: The rotation angle for + and - symbols (typically 20-30°)

Turtle Graphics Interpretation

The generated string is interpreted by a turtle that moves and rotates in 3D space:

  • F or G: Move forward and draw a line (branch)

  • +: Turn left (yaw) by angle

  • -: Turn right (yaw) by angle

  • [: Push current state (position, direction, up vector) onto a stack

  • ]: Pop state from stack (return to previous position and orientation)

  • >: Pitch up

  • <: Pitch down

  • &: Roll left

  • ^: Roll right

  • L: Draw a leaf (simplified as a sphere)

  • !: Decrease branch thickness

  • ': Decrease branch length

Implementation Details

String Generation

The L-system string is generated by iteratively applying production rules:


function generateLSystem(rule: LSystemRule): string {

let result = rule.axiom;

for (let i = 0; i < rule.iterations; i++) {

let newResult = '';

for (const char of result) {

newResult += rule.rules[char] || char;

}

result = newResult;

}

return result;

}

Each iteration expands the string according to the rules. For example, with axiom "F" and rule F → F[+F]F[-F]F:

  • Iteration 0: F

  • Iteration 1: F[+F]F[-F]F

  • Iteration 2: 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

  • And so on...

3D Turtle Graphics

The turtle maintains state including position, direction, and up vector for proper 3D rotation:


interface TurtleState {

position: THREE.Vector3;

direction: THREE.Vector3; // Forward direction

up: THREE.Vector3; // Up direction for pitch/roll

length: number; // Current branch length

radius: number; // Current branch radius

}

Branch Generation

Branches are created as cylinders with decreasing radius and length:


// Create branch segment

const branchGeometry = new THREE.CylinderGeometry(

currentRadius,

currentRadius * 0.9, // Taper

currentLength,

6 // Segments for performance

);

const branch = new THREE.Mesh(branchGeometry, trunkMaterial);

Each branch is positioned and oriented based on the turtle's current state, creating a continuous tree structure.

State Stack

The [ and ] symbols use a stack to save and restore turtle state, enabling branching:


const stateStack: TurtleState[] = [];

  

// Push state

if (char === '[') {

stateStack.push({

position: turtle.position.clone(),

direction: turtle.direction.clone(),

up: turtle.up.clone(),

length: turtle.length,

radius: turtle.radius,

});

}

  

// Pop state

if (char === ']') {

const savedState = stateStack.pop();

if (savedState) {

turtle.position = savedState.position;

turtle.direction = savedState.direction;

turtle.up = savedState.up;

turtle.length = savedState.length;

turtle.radius = savedState.radius;

}

}

Predefined L-System Configurations

Simple Branching


Axiom: F

Rule: F → F[+F]F[-F]F

Angle: 25°

Iterations: 4

Creates a basic ternary branching pattern with three branches at each node.

Complex Branching


Axiom: F

Rule: F → FF+[+F-F-F]-[-F+F+F]

Angle: 22.5°

Iterations: 4

Produces denser branching with more organic structure, using longer segments and more complex branching patterns.

Recursive Tree


Axiom: X

Rules:

X → F+[[X]-X]-F[-FX]+X

F → FF

Angle: 25°

Iterations: 5

Uses a recursive structure with a non-terminal symbol X that expands into more complex patterns, creating self-similar structures.

Bushy Tree


Axiom: F

Rule: F → F[+F][-F][>F][<F]

Angle: 20°

Iterations: 4

Creates four-way branching (left, right, up, down) for dense foliage, using pitch controls (> and <) for vertical branching.

Custom L-System Input

The visualization allows users to enter custom axioms and rules to experiment with different tree structures. The system supports:

  • Multiple rules: Define rules for different symbols (e.g., F → FF, X → F+[[X]-X]-F[-FX]+X)

  • Flexible syntax: Use any symbols, though standard turtle graphics symbols have special meaning

  • Real-time updates: Changes to rules or iterations update the tree immediately

Performance Considerations

L-system tree generation can be computationally expensive, especially with high iteration counts:

  • String length: Grows exponentially with iterations (e.g., 3 iterations → ~1000 chars, 5 iterations → ~100,000 chars)

  • Geometry complexity: Each branch segment requires geometry creation

  • Memory usage: State stack and geometry objects consume memory

Optimizations used:

  • Limited iterations: Default to 3-5 iterations for interactive visualization

  • Low-poly geometry: Use 6 segments for cylinders instead of high-resolution

  • Throttled updates: Regenerate tree only when parameters change

  • Geometry reuse: Reuse materials across branches

Comparison with Simplified Trees

The animated background uses simplified geometric trees for performance, while L-systems provide more realistic and varied tree structures. The trade-off:

  • L-systems: More realistic, infinite variety, but computationally expensive

  • Simplified trees: Fast, good enough for background, but less variety

For the background, simplified trees are preferred because:

  • Thousands of trees need to be rendered

  • Performance is critical

  • Trees are viewed from a distance where detail is less important

References

Academic Papers

  • Lindenmayer, A. (1968). "Mathematical Models for Cellular Interactions in Development." Journal of Theoretical Biology, 18(3), 280-315. DOI - Original L-system paper

  • Prusinkiewicz, P., & Lindenmayer, A. (1990). The Algorithmic Beauty of Plants. Springer-Verlag. - Comprehensive book on L-systems and plant modeling

  • Prusinkiewicz, P., et al. (2001). "The Use of Positional Information in the Modeling of Plants." ACM SIGGRAPH Computer Graphics, 35(3), 289-300. - Advanced L-system techniques

Online Resources

Interactive Tools

Related Articles

Future Enhancements

Potential improvements to the L-system implementation:

  • Stochastic L-systems: Add randomness to rules for more natural variation

  • Parametric L-systems: Use parameters in rules for more control

  • Context-sensitive L-systems: Rules that depend on neighboring symbols

  • Ecosystem simulation: Multiple trees with competition and growth

  • Seasonal variation: Trees that change appearance over time

  • Wind effects: Animated trees that respond to wind

  • Leaf placement: More sophisticated leaf generation and placement

  • Bark texture: Procedural bark textures for more realism

Related Content

The 3D Background

The 3D Background The animated 3D background is a procedurally generated landscape that creates an infinite, dynamically rendered terrain with fractal trees, atmospheric effects, and interactive camer...