LSys a Lindenmayer system implementation


Inherits from: Object


LSys is a Lindenmayer system implementation for SuperCollider. It does parallel rewriting, supports context free, context sensitive, stochastic and parametric rulesets. When working with context sensitive rules in a bracketed L-system, this class takes axial node points and segment neighborhood into account (which is not represented in the sequential string representation naturally). See the examples in this document.


To learn more about L-Systems and the syntax, you might want to check out the book The Algorithmic Beauty of Plants which can be obtained for free from: http://algorithmicbotany.org/papers/#abop


For an interesting read on musical applications of L-Systems on various time scales, the Master's Thesis of Stelios Manousakis - Musical L-Systems is recommended: http://www.modularbrains.net/research.html


See also: LSPlant


Creation / Class Methods


*new (argAxiom, argRules, argIgnore, argEnvironment)

argAxiom - The base axiom. Should be a string.

argRules - Rules for the system. Should be an Array of Associations.

argIgnore - Characters to be ignored when matching context sensitive rules. When working with bracketed context sensitive systems, you should include the [ and ] characters here exclusively. 

argEnvironment - Environment for constants (or other variables) that are to be used by parametric L-Systems.

// simple example, fibonacci numbers

a = LSys("A", ["A" -> "B", "B" -> "AB"]);

10.do({ a.applyRules(1).postln; });



Accessing Instance and Class Variables

axiom_(arg1)

axiom

Sets/gets the axiom. This variable contains the original axiom in string format. arg1 should be String.

rules_(arg1)

rules

Sets/gets the rules array to be used for reproduction. If changed between iterations, Table L-Systems can be simulated with this.

environ_(arg1)

environ

Gets/sets the environment that the functions of parametric L-systems work in. Should be a type of Environment.

ignores_(arg1)

ignores

Gets/sets the ignore characters put inside a String.

currentAxiom

Gets the current product of the system after the rules are applied.

Methods


applyRules (argLevel)

Applies the rules to the current axiom/product. argLevel sets the number of iterations.

Default value is 1.



giveParsedString

Parses and returns the product in an array. Each character is a symbol in the array, and in a parametric system, the parametric elements are returned as an array (packed symbol and arguments). For example:

"ABCF(8,9)K"; //is returned as:

['A', 'B', 'C', ['F', [8, 9]], 'K'];




Context free examples:


//Koch Snowflake

(

a = LSys("F--F--F", ["F" -> "F+F--F+F"]);

k = a.applyRules(4);

LSPlant(80@170, 90, argAngle: 60).drawSize_(4).draw(k);

)


//Dragon Curve

(

a = LSys("L", ["L" -> "L+R+", "R" -> "-L-R"]);

k = a.applyRules(11);

LSPlant(180@270, 180, argAngle: 90).drawSize_(4).draw(k);

)


//Sierpinsky triangle

(

a = LSys("L", ["L" -> "R+L+R", "R" -> "L-R-L"]);

k = a.applyRules(6);

LSPlant(60@400, 30, argAngle: 60).drawSize_(6).draw(k);

)


//Islands and Lakes from Algorithmic Beauty of Plants fig. 1.8.

(

a = LSys("F+F+F+F", ["F" -> "F+f-FF+F+FF+Ff+FF-f+FF-F-FF-Ff-FFF", "f" -> "ffffff"]);

k = a.applyRules(2);

LSPlant(360@350, 0, argAngle: 90).drawSize_(6).draw(k);

)


//Quadratic Snowflake from A.B.O.P. fig. 1.7b

(

a = LSys("F", ["F" -> "F+F-F-F+F"]);

k = a.applyRules(5);

LSPlant(20@350, 90, 90, Rect(300, 300, 530, 500), { Color.rand; }).drawSize_(2).draw(k);

)


//Fig 1.9a. from A.B.O.P.

(

a = LSys("F-F-F-F", ["F" -> "FF-F-F-F-F-F+F"]);

k = a.applyRules(3).postln;

LSPlant(270@120, 90, 90, argColor: { Color.rand; }).drawSize_(6).draw(k);

)


Context sensitive example:


//Signal propagation throughout a string of symbols:


(

a = LSys("baaaaaaaaaaaaaaaa", ["b<a" -> "b", "b" -> "a"]);

16.do

({

a.applyRules(1).postln;

});

)


For the syntax of the context sensitive rule definitions, refer to page 30 in the Algorithmic Beauty of Plants.


Branching (bracketed) examples:


//Tree b from fig 1.24 in A.B.O.P.

(

a = LSys("F", ["F" -> "F[+F]F[-F][F]"]);

k = a.applyRules(5).postln;

LSPlant

(

argAngle: 20, 

argColor: 

{ 

[

Color.green(0.6.rand + 0.4),

Color.gray(0.6.rand)

].choose; 

}

).drawSize_(6).draw(k);

)


//Tree c from fig 1.24 in A.B.O.P.

(

a = LSys("F", ["F" -> "FF-[-F+F+F]+[+F-F-F]"]);

k = a.applyRules(4).postln;

LSPlant

(

argAngle: 22.5, 

argColor: 

{ 

[

Color.green(0.6.rand + 0.4),

Color.gray(0.6.rand)

].choose; 

}

).drawSize_(6).draw(k);

)


//Tree f from fig 1.24 in A.B.O.P.

(

a = LSys("X", ["X" -> "F-[[X]+X]+F[+FX]-X", "F" -> "FF"]);

k = a.applyRules(5).postln;

LSPlant

(

argAngle: 22.5, 

argColor: 

{ 

[

Color.green(0.6.rand + 0.4),

Color.gray(0.6.rand)

].choose; 

}

).drawSize_(4).draw(k);

)



Stochastic L-Systems:


The notation for the stochastic L-systems is a bit different from the standard notation at this time. You need to define a single rule for a single symbol and make the necessary probability calculations in an associated function. this offers more flexibility and arguably provides a better interface for SC. For characters associated with stochastic production rules, you need to append opening and closing parenthesis to the symbol to identify it's association as a function. From the associated function, you need to return the resulting string.


This example is a stochastic version of the tree from A.B.O.P. (fig 1.24d) taken from: http://hardlikesoftware.com/projects/lsystem/lsystem.html


It branches either left or right at the first branch point.


(

a = LSys("X()", ["X()" -> { ["F[+X()]F[-X()]+X()", "F[-X()]F[-X()]+X()"].choose; }, "F" -> "FF"]);

k = a.applyRules(7).postln;

LSPlant

(

argAngle: 20, 

argColor: 

{ 

[

Color.green(0.6.rand + 0.4),

Color.gray(0.6.rand)

].choose; 

}

).drawSize_(2).draw(k);

)


//Tree from fig 1.27 in A.B.O.P. Stochastic tree that will look different each time it's generated.

(

a = LSys("F()", 

[

"F()" -> 

{ 

[

"F()[+F()]F()[-F()]F()",

"F()[+F()]F()",

"F()[-F()]F()"

].choose; 

}

]);

k = a.applyRules(5).postln;

LSPlant

(

argAngle: 25, 

argColor: 

{ 

[

Color.green(0.6.rand + 0.4),

Color.gray(0.6.rand)

].choose; 

}

).drawSize_(5).draw(k);

)


Parametric L-Systems:


Rules of parametric L-systems are defined similar to the stochastic ones. The functions run in the supplied environment. The following example is the Row of Trees model from A.B.O.P. (fig 1.37). Formal definition is taken from: http://hardlikesoftware.com/projects/lsystem/lsystem.html


/*

Constants: 

  c = 1, p = 0.3, q = c − p, 

  h = (p * q) ^ ∧ 0.5

Symbols:

  F(d) forward(d)

  + left(a)

  - right(a)

Axiom: F(d)

Rules:

  F(x) --> F(x * p) + F(x * h) - - 

           F(x * h) + F(x * q)

*/


In this system, the distance to be travelled in each symbol is bound to a variable. The class LSPlants cannot draw parametric strings at this time so it is not used in this example.


(

a = LSys

(

"F(1)", 

["F(x)" -> {|x| "F(%)+F(%)--F(%)+F(%)".format(x * ~p, x * ~h, x * ~h, x * ~q); }],

"",

Environment.make({ ~c = 1; ~p = 0.3; ~q = ~c - ~p; ~h = (~p * ~q) ** 0.5; })

);

k = a.applyRules(5);

k = a.giveParsedString;

)


(

c = false;

d = true;

p = 10@350;

h = 90;

a = 86;

l = 5;

w = Window("Row of trees", Rect(300, 300, 800, 400)).front;

u = UserView(w, w.view.bounds).background_(Color.black)

.clearOnRefresh_(false)

.drawFunc_

({

var dx, dy, nextP;

if(c,

{

Pen.color = Color.white;

dx = ((h * (pi / 180)).sin * l).round;

dy = ((h * (pi / 180)).cos * l).round;

nextP = (p.x + dx)@(p.y + dy);

if(d,

{

Pen.line(p, nextP);

Pen.stroke;

});

p = nextP;

c = false;

});

});



{

({

k.do

({|item|

if(item.species == Array,

{

item[0].switch

(

'F',

{

c = true;

d = true;

l = (item[1][0] * 500);

u.refresh;

0.01.wait;

}

);

},

{

item.switch

(

'+',

{

h = (h + a).wrap(0, 360);

},

'-',

{

h = (h - a).wrap(0, 360);

}

)

});

});

}).fork(AppClock);

}.value;

)


In parametric L-systems, parametric symbols are matched by the number of arguments they have. A simple example, formal definition from page 42 of A.B.O.P.:


/*

axiom: B(2)A(4,4)

p1   : A(x,y) : y<=3 -> A(x*2,x+y)

p2   : A(x,y) : y>3  -> B(x)A(x/y,0)

p3   : B(x)   : x<1  -> C

p4   : B(x)   : x>=1 -> B(x-1) 

*/


(

a = LSys("B(2)A(4,4)", 

[

"A(X,Y)" ->

{|x, y|

if(y <= 3,

{

"A(%,%)".format(x*2, x+y);

},

{

"B(%)A(%,0)".format(x,x/y);

});

},

"B(X)" ->

{|x, y|

if(x < 1,

{

"C";

},

{

"B(%)".format(x-1);

});

}

]);

4.do

({

a.applyRules(1).postln;

});

)


Context sensitive bracketed L-systems:


In context sensitive bracketed systems, LSys takes segment neighborhood into account which isn't immediately visible to the eye in the string itself. To give an example (from figure 1.29 of A.B.O.P.), the production rule BC<S>G[H]M should apply to the string ABC[DE][SG[HI[JK]L]MNO] by successfully skipping the symbols [DE] in the search for  left context and I[JK]L in the search for the right context.


(//in context sensitive bracketed systems, the symbols [] should be explicitly ignored

a = LSys("ABC[DE][SG[HI[JK]L]MNO]", ["BC<S>G[H]M" -> "X"], "[]");

a.applyRules(1).postln; //S turned into X

)


Branching with brackets in a context sensitive domain allow us to obtain more complex forms. Examples are fig 1.31 (a, b, c, d, e) from A.B.O.P. Be aware that due to the high numbers of iterations, these examples might take a few seconds to compute.


(

a = LSys("F1F1F1",

[

"0<0>0" -> "0",

"0<0>1" -> "1[+F1F1]",

"0<1>0" -> "1",

"0<1>1" -> "1",

"1<0>0" -> "0",

"1<0>1" -> "1F1",

"1<1>0" -> "0",

"1<1>1" -> "0",

"+" -> "-",

"-" -> "+"

], "+-F[]");

k = a.applyRules(30);

LSPlant(argAngle: 22.5, argColor: { [Color.green(1.0.rand, rrand(0.5, 1)), Color.gray(1.0.rand, rrand(0.5, 1))].choose }).drawSize_(4).draw(k);

)


(//this will take a while to compute. beauty comes at a price. ^_^

a = LSys("F1F1F1",

[

"0<0>0" -> "1",

"0<0>1" -> "1[-F1F1]",

"0<1>0" -> "1",

"0<1>1" -> "1",

"1<0>0" -> "0",

"1<0>1" -> "1F1",

"1<1>0" -> "1",

"1<1>1" -> "0",

"+" -> "-",

"-" -> "+"

], "+-F[]");

k = a.applyRules(30);

LSPlant(argAngle: 22.5, argColor: { [Color.green(1.0.rand, rrand(0.5, 1)), Color.gray(1.0.rand, rrand(0.5, 1))].choose }).drawSize_(6).draw(k);

)


(

a = LSys("F1F1F1",

[

"0<0>0" -> "0",

"0<0>1" -> "1",

"0<1>0" -> "0",

"0<1>1" -> "1[+F1F1]",

"1<0>0" -> "0",

"1<0>1" -> "1F1",

"1<1>0" -> "0",

"1<1>1" -> "0",

"+" -> "-",

"-" -> "+"

], "+-F[]");

k = a.applyRules(26);

LSPlant(argAngle: 25.75, argColor: { [Color.green(1.0.rand, rrand(0.5, 1)), Color.gray(1.0.rand, rrand(0.5, 1))].choose }).drawSize_(6).draw(k);

)


(

a = LSys("F0F1F1",

[

"0<0>0" -> "1",

"0<0>1" -> "0",

"0<1>0" -> "0",

"0<1>1" -> "1F1",

"1<0>0" -> "1",

"1<0>1" -> "1[+F1F1]",

"1<1>0" -> "1",

"1<1>1" -> "0",

"+" -> "-",

"-" -> "+"

], "+-F[]");

k = a.applyRules(24);

LSPlant(argAngle: 25.75, argColor: { [Color.green(1.0.rand, rrand(0.5, 1)), Color.gray(1.0.rand, rrand(0.5, 1))].choose }).drawSize_(4).draw(k);

)


(

a = LSys("F1F1F1",

[

"0<0>0" -> "0",

"0<0>1" -> "1[-F1F1]",

"0<1>0" -> "1",

"0<1>1" -> "1",

"1<0>0" -> "0",

"1<0>1" -> "1F1",

"1<1>0" -> "1",

"1<1>1" -> "0",

"+" -> "-",

"-" -> "+"

], "+-F[]");

k = a.applyRules(26);

LSPlant(argAngle: 22.5, argColor: { [Color.green(1.0.rand, rrand(0.5, 1)), Color.gray(1.0.rand, rrand(0.5, 1))].choose }).drawSize_(6).draw(k);

)