Creative Coding for Surpac - L-Systems

Dragon Curve

Hi Everyone,

Today's post is looking at L-Systems and how to code these in Surpac using typical L-System symbology.  If you haven't seen the previous "Creative Coding" post, L-Systems (Lindenmayer Systems) were originally used to model the growth processes of plants and algae, however, we can learn a lot about different coding techniques by looking into L-Systems.  For example, the macro attached to this post contains;

  • Recursion (Procedures that call themselves to solve a problem)
  • GUIDO form generation including action call backs and colour text
  • SCL Creation of Layers, Strings, Segments and Points
  • TCL list creation and searching
  • Branching (if statements)
  • For loops

At the heart of the L-System is a drawing program.  (Turtle proc in the macro) It takes a sequence of symbols (Otherwise known as the sentence) and will draw what those symbols represent on the screen.  The symbols tell the drawing program to either move forward (F), rotate left (-), rotate right (+), store the current position ([) or move back to a previous position(]).  That's it!  It's amazing to me what can be drawn by such simple instructions.

First of all, the form.  There are 14 different L-Systems to choose from.  Each L-System requires an "Axiom" some "Rules" a "Rotation angle" and the number of "Iterations".  

  • The Axiom is the starting condition, "X" in the above form.
  • The Rules tell the drawing program how to move.  i.e. The rule for X is "F+[[X]-X]-F[-FX]+X" (More on this in a bit)
  • The Rotation is the angle to turn either left (-) or right (+) in this case 25 degrees
  • The Iterations controls how many times we will repeat the rules.

To expand this further, when the program sees the axiom "X" it generates the sentence, "F+[[X]-X]-F[-FX]+X".  When the program sees "F" it generates the sentence "FF". 

So the program starts by reading the Axiom "X" and generating sentence "F+[[X]-X]-F[-FX]+X".  The second iteration will read the sentence generated in the first iteration so the sentence becomes "FF+[[F+[[X]-X]-F[-FX]+X]-F+[[X]-X]-F[-FX]+X]-FF[-FFF+[[X]-X]-F[-FX]+X]+F+[[X]-X]-F[-FX]+X"

I hope you can see how quickly the sentence can grow from some basic rules.  This sentence is then given to the "Turtle" drawing program which executes the sentence commands. i.e. F = Move forward, + = Turn right 25 degrees, - = Turn left 25 degrees, [ = store current position, ] = Return to previous position.

There is a lot more that could be said but I'll leave you with some code snippets and some of my favourite L-Systems.  Enjoy!

Code below shows the if statement setting the required variables for the L-System.  

if {\$lsystem == "Crystal Growth"} {
  set axiom "F+F+F+F"
  set rules [dict create F "FF+F++F+F" + "+" - "-"]
  set rotationAngle 90
  set iterations 4
}

This procedure takes the current sentence and generates a new sentence by applying the rules to it.

proc applyRules {sentence rules} {
  foreach char [split \$sentence ""] {
    if {[dict exists \$rules \$char]} {
      append newSentence [dict get \$rules \$char]
    } else {
      append newSentence \$char
    }
  }
  return \$newSentence
}

The "Turtle" procedure used to draw the L-System.

proc turtle {fractalStr fractalSeg sentence stack} {
  global rotationAngle
  set lastX 100
  set lastY 100
  set lastBearing 0
  set lastDist 100

  for {set i 0} {\$i < [string length \$sentence]} {incr i} {;#Iterate through all the characters in the string called "sentence"
    set currentCharacter [string index \$sentence \$i]
    #puts "Character = \$currentCharacter"
    if {\$currentCharacter == "F" || \$currentCharacter == "G" || \$currentCharacter == "A" || \$currentCharacter == "B"} {;# Move forward using the bearing and length stored in the stack.  Index 2 for bearing and 3 for length
      #Get last point in the stack, move forward and then append new point in the stack
      set calcPoint [_raypoint \$lastBearing 0 \$lastDist \$lastX \$lastY 0]
      newPoint \$fractalSeg [lindex \$calcPoint 0] [lindex \$calcPoint 1] 0;# Create New point of fractal
      set lastX [lindex \$calcPoint 0]
      set lastY [lindex \$calcPoint 1]
    } elseif {\$currentCharacter == "\+"} {
      #Get last position in the stack and update the bearing of the turtle.
      set lastBearing [expr \$lastBearing+\$rotationAngle]
      if {\$lastBearing >= 360} {
        set lastBearing [SclExpr \$lastBearing - 360]
      }
    } elseif {\$currentCharacter == "\-"} {
      #Get last position in the stack and update the bearing of the turtle.
      set lastBearing [expr \$lastBearing-\$rotationAngle]
      if {\$lastBearing < 360} {
        set lastBearing [SclExpr \$lastBearing + 360]
      }
    } elseif {\$currentCharacter == "\["} {
      #store the current position of the turtle to start from later.
      lappend restoreStack [list \$lastX \$lastY \$lastBearing \$lastDist]
      #puts "RestoreStack = \$restoreStack"

    } elseif {\$currentCharacter == "\]"} {
      #Move turtle back to the restore point
      set restorePoint [lindex \$restoreStack end]
      #puts "Restore Point = \$restorePoint"
      set lastX [lindex \$restorePoint 0]
      set lastY [lindex \$restorePoint 1]
      set lastBearing [lindex \$restorePoint 2]
      set lastDist [lindex \$restorePoint 3]
      set restoreStack [lrange \$restoreStack 0 end-1]
      \$fractalStr SclCreateSegment fractalSeg [\$fractalStr SclCountItems]
      newPoint \$fractalSeg \$lastX \$lastY 0;# Create New point of fractal
    }
  }
}

Some of my favourite L-Systems.

Barnsley Fern
Sierpinski Triangle
Gosper Curve
Hilbert Curve

 

Zip file containing macro for testing