In this post I'm going to show a little program for displaying graphical representations of L-Systems using turtle graphics implemented in F#, C# and WPF.

First of all this program could be easily implemented using only F#, but for me is interesting to see the interaction between native F# type/structures and C#. Because of this, the code that performs the L-system rewrite is written in F# and the code that takes the result is written in C# and uses WPF.

The first thing we need is an implementation of the turtle. Since we want to use this library with several graphics toolkits, we define our own point type for the generated data.

#light

namespace Langexplr.Lsystems

open System

open Microsoft.FSharp.Math.Vector

type Point =

{x : int; y : int }

module Funcs = begin

let my_create_vector(i,j) =

let result = (create 2 0.0)

result.[0] <- i

result.[1] <- j

result

end

type TurtleGraphics =

class

val mutable direction : vector

val mutable position : Point

new(iX,iY) = { position = {x = iX; y = iY};

direction = Funcs.my_create_vector(1.0,0.0)}

member t.Position

with get() = t.position and

set(v) = t.position <- {x = v.x; y = v.y }

member t.Direction

with get() = t.direction and

set(v) = t.direction <- v

member t.Advance(distance : int) =

let aX = int_of_float (t.direction.[0] * float_of_int distance)

let aY = int_of_float (t.direction.[1] * float_of_int distance)

t.position <- { x = aX+t.position.x;

y = aY+t.position.y }

t.position

member t.Rotate(angle) =

let nI = (t.direction.[0] * Math.Cos(angle)) - (t.direction.[1] * Math.Sin(angle))

let nJ = (t.direction.[0] * Math.Sin(angle)) + (t.direction.[1] * Math.Cos(angle))

t.direction <- Funcs.my_create_vector(nI,nJ)

end

Now we need to represent the elements required for the L-Systems. The following elements are required:

Start point or axiom | the initial sequence of elements |

Rules | L-system substitution rules |

Angle | The angle used when rotating the turtle |

Number of iterations | The number of times the rules will be applied to the axiom |

Size of the initial segment | The size in pixels of the line that is drawn when the turtle moves forward |

Also the elements inside the rule and the axiom must be translated to turtle graphics commands. The following commands are supported:

`|` | Draws a line forward, the size of the line inversely proportional to the iteration number |

`+` | Turn left by the specified angle |

`-` | Right left by the specified angle |

`[` | Saves the position and direction of the turtle in a stack |

`]` | Restores the position and direction of the turtle from the stack |

Letter | If activated, draws a line forward |

The following code shows the implementation of this:

#light

namespace Langexplr.Lsystems

open Langexplr.Lsystems

open System

type LsystemElement =

| Var of String

| Constant of String

| PipeCommand of int

type Rule =

| Rule of LsystemElement * LsystemElement list

module LsystemFuncs = begin

let rec gettingLsystemElements (str:string) i result =

if str.Length > i then

if System.Char.IsLetterOrDigit(str.[i]) then

gettingLsystemElements str (i+1) ((Var(str.[i].ToString()))::result)

else

gettingLsystemElements str (i+1) ((Constant(str.[i].ToString()))::result)

else

List.rev result

let getLsystemElements str =

gettingLsystemElements str 0 []

end

type TurtleGraphicsLsystemProcessor =

class

val start : LsystemElement list

val angle : double

val rules : Rule list

val seg_size : int

val mutable saved_positions : Point list

val mutable saved_directions : vector list

val mutable drawVariables : bool

new (a_start,a_angle,t_rules,s_size,drawVars) = {

start = a_start;

angle = (Math.PI/180.0)* a_angle;

rules = t_rules;

seg_size = s_size;

saved_positions = [];

saved_directions = [];

drawVariables = drawVars}

member lp.generate_for n current =

match n with

| 0 -> current

| o -> lp.generate_for (n - 1) (lp.apply_rules current n)

member lp.apply_rules elements iteration =

match elements with

| ((Var v)::rest) -> List.append (lp.apply_rule_for v) (lp.apply_rules rest iteration)

| ((Constant "|")::rest) -> (PipeCommand iteration)::(lp.apply_rules rest iteration)

| (e::rest) -> e::(lp.apply_rules rest iteration)

| [] -> elements

member lp.apply_rule_for v =

match (List.tryfind (fun r -> match r with

| Rule(Var vvar,_) when vvar = v -> true

| _ -> false)

lp.rules) with

| Some (Rule(_, result)) -> result

| None -> [Var v]

member lp.generate_iteration n (tg:TurtleGraphics)=

let final = lp.generate_for n lp.start

in

List.rev(lp.generate_points n final tg [tg.Position] [])

member lp.generate_points iterations elements (tg:TurtleGraphics) current lines =

match elements with

| ((Var _)::rest) ->

if (lp.drawVariables) then

tg.Advance(lp.seg_size)

lp.generate_points iterations rest tg (tg.Position::current) lines

else

lp.generate_points iterations rest tg current lines

| ((Constant "+")::rest) ->

tg.Rotate(lp.angle)

lp.generate_points iterations rest tg current lines

| ((Constant "-")::rest) ->

tg.Rotate(-1.0*lp.angle)

lp.generate_points iterations rest tg current lines

| ((Constant "[")::rest) ->

lp.saved_directions <- tg.Direction::lp.saved_directions

lp.saved_positions <- tg.Position::lp.saved_positions

lp.generate_points iterations rest tg current lines

| ((Constant "]")::rest) ->

match (lp.saved_directions,lp.saved_positions) with

| (cdir::rest_dir,cpos::rest_pos) ->

lp.saved_directions <- rest_dir

lp.saved_positions <- rest_pos

tg.Position <- cpos

tg.Direction <- cdir

lp.generate_points iterations rest tg [tg.Position] (current::lines)

| _ -> lp.generate_points iterations rest tg current lines

| ((Constant "|")::rest) ->

tg.Advance(lp.seg_size / (iterations) )

lp.generate_points iterations rest tg (tg.Position::current) lines

| ((PipeCommand iteration)::rest) ->

tg.Advance(lp.seg_size / (iterations - iteration) )

lp.generate_points iterations rest tg (tg.Position::current) lines

| (_::rest) -> lp.generate_points iterations rest tg current lines

| [] -> current::lines

end

The

`generate_iteration`

method is the one that generates the line information. Its result is a list of lists of elements of type `Point`

.The C# code that takes this F# list of lists of Points and converts it to a group of Polyline instances is the following:

private void b_Click(object sender, RoutedEventArgs e)

{

string origin = this.originTB.Text;

int originX = 50;

int originY = 50;

string[] oparts = origin.Split(',');

if (oparts.Length == 2)

{

originX = int.Parse(oparts[0]);

originY = int.Parse(oparts[1]);

}

TurtleGraphics tg = new TurtleGraphics(originX, originY);

int iterations = int.Parse(this.iterationsTB.Text);

this.canvas1.Children.Clear();

List<Rule> rules = GetRules();

TurtleGraphicsLsystemProcessor tgls =

new TurtleGraphicsLsystemProcessor(

LsystemFuncs.getLsystemElements(this.axiomTextBox.Text),

int.Parse(this.angleTB.Text),

Microsoft.FSharp.Collections.ListModule.of_IEnumerable<List<Rule>, Rule>(rules),

int.Parse(this.segmentSizeTB.Text),

drawVariablesCB.IsChecked == true);

var pointCollections =

from pc in (tgls.generate_iteration(int.Parse(this.iterationsTB.Text))).Invoke(tg)

select (new PointCollection(

from p in pc

select new System.Windows.Point(p.x, p.y)));

foreach (PointCollection pcol in pointCollections)

{

Polyline pLine = new Polyline();

pLine.Points = pcol;

pLine.Stroke = this.colorButton.Background;

this.canvas1.Children.Add(pLine);

}

}

What is interesting to see is that the list generated in F# is easily manipulated using LINQ. The expression that sets the value of

`pointCollections`

takes the native list of lists of points and converts it to a list of PointCollection objects in one expression .Another interesting thing about the interaction between F# and C# is that the definition of the

`generate_iteration`

says that it could be applied with one or two arguments (because of Currying) this is used in C# by invoking the result of calling the method with one argument: `(tgls.generate_iteration(int.Parse(this.iterationsTB.Text))).Invoke(tg)`

.Executing the program with the following L-system (from Wikipedia):

Also with using the "|" command:

Code for this experiment can be found here.

## 4 comments:

Wow, good stuff. Can't wait to dig into the code a bit.

Thanks for the Jump start. I just found F# today and I'm looking to implement the CGA Shape grammar (inhanced LSystem) for building modeling in XNA.

Hello!

I have a problem compiling the code.

It gives me the error: Error: The namespace or module 'Langexplr' is not defined lsystemgenerator.fs

I tryed to mix up, or open the namespaces but it won't compile.

Can you help me please? You can contact via my e-mail: timotei21 [at] gmail.com

There's a a slightly newer version of the code here

Post a Comment