Experiments with ruby-processing (processing-2.2.1) and JRubyArt for processing-3.0

Monday 18 January 2010

Using a symbolic grammar for LSystems ruby-processing

Here I map characters of the LSystem grammar to human readable symbols. This I do by creating a hash. I then convert axiom strings to an array of symbols. The grammar is then "produced" as an array of symbols. The renderer the interprets the production as an array of symbols (it might use less memory than non-symbolic version but is it any faster???). I don't seem to be able use collect, so I need to create a new array of symbols when generating the grammar.


##
# A Sierpinski Triangle implemented using a
# Lindenmayer System in ruby-processing by Martin Prout
###
require 'symbol'

class Sierpinski_Test < Processing::App
  load_library :grammar
  
  attr_reader :sierpinski
  
  def setup
    size 610, 600
    @sierpinski = Sierpinski.new
    sierpinski.create_grammar(8)  
    no_loop
  end
  
  def draw
    background 0
    sierpinski.render
  end
end
############################
# sierpinski.rb
###########################
class Sierpinski
  include Processing::Proxy
 
  attr_accessor :axiom, :grammar, :start_length, :production, :draw_length, :xpos, :ypos, :symbol_map
  XPOS = 0
  YPOS = 1
  ANGLE = 2
  DELTA = (Math::PI/180) * 120                 # convert degrees to radians
  
  def initialize
    @start_length = 300  
    setup_grammar
    @xpos = width/60
    @ypos = height*0.9
    stroke 255
    @draw_length = start_length
  end

  def setup_grammar
    @axiom = "FX"
    @symbol_map = {"X" => :X, "+" => :PLUS, "-" => :MINUS, "F" => :FORWARD}
    @grammar = Grammar.new(axiom, symbol_map)
    grammar.add_rule "F", "FF"                  # replace F rule see grammar library
    grammar.add_rule "X", "-FXF+FXF+FXF-"       # replace X rule see grammar library
    @production = axiom
  end

  def render                       # NB not using affine transforms here
    turtle = [xpos, ypos, 0.0]
    production.each do |element|
      case element
      when :FORWARD                    
        turtle = draw_line(turtle, draw_length)
      when :PLUS
        turtle[ANGLE] += DELTA   # rotate by + theta if going the affine transform route
      when :MINUS
        turtle[ANGLE] -= DELTA   # rotate by - theta if going the affine transform route
      when :X                     # do nothing except recognize :X as a word in the L-system grammar
      else 
              puts "Character '#{element}' is not in grammar" 
      end
    end
  end
 
  ##############################
  # create grammar from axiom and
  # rules (adjust scale)
  ##############################
  
  def create_grammar(gen)
    @draw_length *=  0.5**gen
    @production = grammar.generate gen
  end

  private
  ######################################################
  # draws line using current turtle and length parameters
  # returns a turtle corresponding to the new position
  ######################################################

  def draw_line(turtle, length)
    new_xpos = turtle[XPOS] + length * Math.cos(turtle[ANGLE])
    new_ypos = turtle[YPOS] + length * Math.sin(turtle[ANGLE])
    line(turtle[XPOS], turtle[YPOS], new_xpos, new_ypos)
    turtle = [new_xpos, new_ypos, turtle[ANGLE]]
  end
end  

############################
# grammar.rb
# Non-stochastic symbolic grammar
# with unique premise/rules
############################
class Grammar
  attr_accessor :axiom, :rules, :symbol_map
  
  def initialize(axiom, symbol_map)
    @symbol_map = symbol_map
    @axiom = string_to_symbol_array(axiom)
    @rules = Hash.new
  end
  
  def string_to_symbol_array(a_string)
    sym_array =   []
    a_string.scan(/./).each do |ch|
      sym_array.push(symbol_map[ch])
    end
    return sym_array
  end
  
  def add_rule premise, rule
    skey = symbol_map[premise]
    svalue = string_to_symbol_array(rule)
    rules.store(skey, svalue)
  end
  
  ##########################################
  # control the number of iterations
  # default 0, returns the axiom
  ##########################################
  def generate repeat = 0
    prod = axiom
    repeat.times do
      prod = new_production(prod)
    end
    return prod
  end
  
  ##########################################
  # replace each pre symbol with un-splatted symbol array
  # from rules lookup
  ##########################################
  def new_production production
    prod = []
    production.each do |c
      (r = rules[c]) ? prod.push(*r) : prod.push(c)
    end
    return prod
  end
end 

No comments:

Post a Comment

Followers

Blog Archive

About Me

My photo
I have developed JRubyArt and propane new versions of ruby-processing for JRuby-9.1.5.0 and processing-3.2.2