In 1968 a biologist, Aristid Lindenmayer, introduced a variant of formal grammars - the Lindenmayer system (or simply L-system). L-system formalism consists of an axiom and derivation rules. Each word (or symbol) in the axiom is rewritten according to rules, thus obtaining a production. Productions then can be rewritten again and again any number of times.
![]() |
(with-graphics (set-axiom (turn(-5) A)) (add-rule (A -> F(50) turn(10) F(50) turn(-10) A)) |
To add an L-system rule, add-rule
macro must be used within with-graphics
. The argument to this
macro is a rule which looks something like (S0 -> S1 S2 S3)
.
That means that each symbol S0
in the current production
will be replaced with symbols S1 S2 S3
in the next
production. Unfortunately the above example draws nothing because turtle
starts interpreting the axiom right away. It turns -5 degrees and skips
symbol A
because there is no drawing command associated with it.
![]() |
(with-graphics (iteration-count 1) (set-axiom (turn(-5) A)) (add-rule (A -> F(50) turn(10) F(50) turn(-10) A)) |
To get the program to rewrite the L-system the
desired number of times, iteration-count
function must be used.
If iteration-count
is not called, iteration count defaults to
zero, which is why nothing was rewritten in the previous example. However,
in this example:
turn(-5) A
is rewritten once:
turn(-5) F(50) turn(10) F(50) turn(-10) A
![]() |
(with-graphics (iteration-count 6) (set-axiom (turn(-5) A)) (add-rule (A -> F(50) turn(10) F(50) turn(-10) A)) |
This example is exactly like the previous one except for the iteration count:
axiom:
turn(-5) A
1st production:
turn(-5) F(50) turn(10) F(50) turn(-10) A
2nd production:
turn(-5) F(50) turn(10) F(50) turn(-10) F(50) turn(10) F(50)
turn(-10) A
3rd production:
turn(-5) F(50) turn(10) F(50) turn(-10) F(50) turn(10) F(50)
turn(-10) F(50) turn(10) F(50) turn(-10) A
...
6th production:
turn(-5) F(50) turn(10) F(50) turn(-10) F(50) turn(10) F(50) turn(-10)
F(50) turn(10) F(50) turn(-10)
F(50) turn(10) F(50) turn(-10) F(50) turn(10) F(50) turn(-10) F(50)
turn(10) F(50) turn(-10) A
![]() |
(defparameter *green* (create-color 0.0 1.0 0.0)) (with-graphics (iteration-count 6) (set-axiom (radius(2) color(*green*) turn(-5) A)) (add-rule (A -> F(50) turn(10) F(50) turn(-10) A)) (set-background-color (create-color 0.0 0.2 0.4))) |
In this example some features that were described in the previous chapter have been added.
![]() |
(defparameter *green* (create-color 0.0 1.0 0.0)) (with-graphics (iteration-count 6) (set-axiom (radius(2) color(*green*) turn(-5) A F(50) obj("blossom" 10.0))) (add-rule (A -> F(50) turn(10) F(50) turn(-10) A)) (set-background-color (create-color 0.0 0.2 0.4)) (read-wavefront "blossom.obj" "blossom.png")) |
It is convenient to design an L-system
so that rules have semantic meaning. In this example, rule A
represents the flower's stem. Everything that comes before this rule will
form the base of the plant, and everything that comes after it is at the
apex of the plant (e.g., the "blossom" object).
![]() |
(defparameter *green* (create-color 0.0 1.0 0.0)) (with-graphics (iteration-count 6) (set-axiom (radius(2) color(*green*) turn(-5) A F(50) obj("blossom" 10.0))) (add-rule (A -> [ roll(180) L ] F(50) turn(10) [ L ] F(50) turn(-10) A)) (set-background-color (create-color 0.0 0.2 0.4)) (read-wavefront "blossom.obj" "blossom.png") (add-macro (L -> turn(60) F(20) obj("leaf" 10.0 *green*))) (read-wavefront "leaf.obj")) |
add-macro
is used to help add leaves to the plant.
Its syntax is very similar to add-rule
, except that
add-macro
can not recursively expand to itself.
L-system macros are expanded into rules
prior to rewriting. For that reason (L -> L)
is a bad macro,
because semantically it will infinitely expand to itself, but practically
LISP will say that "The function L is undefined". Here is an example that
illustrates the differences between add-rule
and add-macro
:
(add-macro (B -> A)) (add-rule (A -> B A)) |
is the same as: | (add-rule (A -> A A)) |
and after 3 steps expands A to: |
(A A A A A A A A) |
while
(add-rule (B -> A)) (add-rule (A -> B A)) |
after 3 steps expands A to: |
(B A A B A) |
![]() |
(defparameter *green* (create-color 0.0 1.0 0.0)) (with-graphics (iteration-count 7) (set-axiom (radius(2) color(*green*) move((create-point 300.0 0.0 0.0)) X)) (add-rule (A -> [ roll(180) L ] F(50) turn(10) [ L ] F(50) turn(-10) A)) (set-background-color (create-color 0.0 0.2 0.4)) (read-wavefront "blossom.obj" "blossom.png") (add-macro (L -> turn(60) F(20) obj("leaf" 10.0 *green*))) (read-wavefront "leaf.obj") (add-rule (P -> A F(50) obj("blossom" 10.0) )) (add-rule (X -> [ turn(-5) P ] turn(-90) jump(120) turn(90) X))) |
To illustrate how an L-system is rewritten,
the last example introduces two new rules: P
which
semantically means "plant", and X
which semantically
means "a row of plants". It also introduces two new turtle commands.
jump
moves turtle forward in the same manner as
forward
, except that jump
does not cause
turtle to leave a trace. The command move
takes a point
structure as argument and "teleports" turtle to that specific location
in absolute coordinates. Bottom middle of the screen where turtle starts
its life has coordinates (create-point 0.0 0.0 0.0)
.
Moving turtle to coordinates (create-point 300.0 0.0 0.0)
moves turtle 300.0 units along the x axis to the right where the base
of the longest plant is.