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

Showing posts with label l-systems. Show all posts
Showing posts with label l-systems. Show all posts

Monday, 25 January 2010

Fern Fractal Using LSystems (includes a subtle color change)

Here is another LSystem example using the control panel to set the number of 'generations' of the fractal. This sketch requires my grammar library see the Cesàro fractal.

##############################################
# fern_test.rb inspired by processing fern by Gareth Spor
##############################################
require 'fern'

class FernTest < Processing::App
  load_libraries :control_panel, :grammar

  attr_accessor :init_color, :fern, :repeat

  def setup()
    size(800, 800)
    setup_panel
    @init_color = color(0, 240, 0)
    redraw
  end

  def draw()
    background(0)
    fern.render(250.0, 650.0, 0.0, 100.0, init_color)
  end

  def setup_panel
    control_panel do |c|
      c.title = "Control:"
      c.menu(:repeat, ['10', '12', '16', '20'], '12') {|m| load_menu_item(m) }
      c.button(:redraw)
    end
  end

  def redraw
    @fern = Fern.new()
    @production = fern.create_grammar(repeat.to_i)
  end

  def load_menu_item(item)
    repeat = item
  end

end

######################
# fern.rb
######################
class Fern
  include Processing::Proxy

  XPOS = 0   # place holders for pen array
  YPOS = 1
  THETA = 2
  LENGTH = 3
  HUE = 4
  DELTA = Math::PI/180 * 35.2 # NB: 36 is boring


  attr_accessor :pen, :production, :grammar, :axiom

  def initialize
    @axiom = "FD"
    @grammar = Grammar.new(axiom)
    grammar.add_rule("D", "C+@FD")
    grammar.add_rule("C", "B")
    grammar.add_rule("B", "[7+#FD][7-#FD]") # abbreviated lsystem grammar
    @production = axiom
    @pen = Array.new(5) # use a simple array for pen (for efficiency)
  end

  def render(x, y, theta, len, col)
    pen[XPOS] = x
    pen[YPOS] = y
    pen[THETA] = -Math::PI/2
    pen[LENGTH] = len
    pen[HUE] = col
    repeats = 1
    stack = Array.new  # use a locally defined array as the pen stack
    @production.each_char do |element|
      case(element)
      when "F"  # move forward
        @pen = draw_line(pen)      
      when "+"  #turn right
        pen[THETA] += (2 * Math::PI) % (repeats * DELTA)
        repeats = 1    
      when '-'  #turn left
        pen[THETA] -= (2 * Math::PI) % (repeats * DELTA)
        repeats = 1    
      when "#"  #resize line length & darken color
        pen[LENGTH] *= 0.33
        pen[HUE] = decrement_color(20)    
      when "@"  #resize line length & darken color
        pen[LENGTH] *= 0.9
        pen[HUE] = decrement_color(10)
      when "["  #push state
        stack.push(@pen.dup)    
      when "]"  #pop state
        @pen = stack.pop()    
      when "7"
        repeats = Integer(element)    
      when "B" # do nothing except confirm character in grammar        
      when "C" # do nothing except confirm character in grammar        
      when "D" # do nothing except confirm character in grammar        
      else
        puts("character '#{element}' not in grammar")    
      end
    end
  end

  def draw_line(pen)
    temp = pen.clone
    temp[XPOS] += temp[LENGTH] * Math.cos(temp[THETA])
    temp[YPOS] += temp[LENGTH] * Math.sin(temp[THETA])
    stroke_weight(2)
    stroke(temp[HUE])
    line(pen[XPOS], pen[YPOS], temp[XPOS], temp[YPOS])
    return temp
  end

  def decrement_color(hue_step)
    gree = green(pen[HUE]) - hue_step
    color(0, gree, 0)
  end

  def create_grammar(gen)
    @production = grammar.generate(gen)
  end

end




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 

Wednesday, 13 January 2010

A 3D Plant using L-Systems and ruby-processing

For the grammar library see my previously posted Cesàro fractal (plant grammar after Hung-Wen Chen). If you are using either Windows or Mac OS then try using opengl instead of P3D. There is an issue with linux such that you need to use fullscreen in combination with opengl, and that for me at least mucks up using the control panel.



########################################################
# A 3D plant implemented using a
# Lindenmayer System in ruby-processing by Martin Prout
########################################################

require 'plant'

class Plant_Test < Processing::App
  load_libraries :grammar, :control_panel
  attr_reader :plant, :rot_z, :rot_y, :rot_x

  def setup()
    size(800, 800, P3D)
    setup_panel
    @plant = Plant.new
    plant.create_grammar(5)
    no_stroke()
    @rot = 0
  end


  def setup_panel
    control_panel do |c|
      c.title = "Control:"
      c.slider :rot_x, 43..63, 53.81
      c.slider :rot_y, 18..38, 28.41
      c.slider :rot_z, -20..20, -0.45
      c.button :reset
    end
  end

  def reset
     @rot_x = 53.81
     @rot_y = 28.41
     @rot_z = -0.45    
  end

  def draw()
    background(0)
    ambient_light(0, 255, 0)
    directional_light(0, 255, 0, 0.0, -1.0, 0.0)
    camera(rot_x, rot_y, rot_z, # eye_x, eye_y, eye_z
      0.0, -50.0, 0.0,            # centerX, centerY, centerZ
      0.0, 1.0, 0.0)
    plant.render()
  end
end

############################
# plant.rb
###########################
class Plant
  include Processing::Proxy

  attr_reader :grammar, :axiom, :production, :premis, :rule,
  :theta, :scale_factor, :distance, :phi

  def initialize()
    @axiom = "F"
    @grammar = Grammar.new(axiom)
    @production = axiom
    @premis = "F"
    @rule = "F[&+F]F[->F][->F][&F]"
    @scale_factor = 0.8
    @distance = 3
    @theta = Math::PI/180 * 28
    @phi = Math::PI/180 * 28
    grammar.add_rule(premis, rule)
    no_stroke()
  end

  def render()
    production.each_char do |ch|
      case(ch)
      when "F"    
        fill(0, 200, 0)
        translate(0, distance/-2, 0)
        box(distance/4 , distance, distance/4)
        translate(0, distance/-2, 0)
      when "-"
        rotateX(-theta)
      when "+"
        rotateX(theta)
      when "&"
        rotateY(-phi%(Math::PI*2))
      when ">"
        rotateZ(phi%(Math::PI*2))
      when "<"
        rotateZ(-phi)
      when "["
        push_matrix
        @distance = distance * scale_factor
      when "]"
        pop_matrix
        @distance = distance / scale_factor
      else
        puts("character '#{ch}' not in grammar")
      end
    end
  end
  ##############################
  # create grammar from axiom and
  # rules (adjust scale)
  ##############################

  def create_grammar(gen)
    @production = @grammar.generate gen
  end
end


Wednesday, 6 January 2010

Adding some controls to Sierpinski sketch


##
# A controllable Sierpinski Variant implemented using a
# Lindenmayer System in ruby-processing by Martin Prout
###
require 'sierpinski'

class Sierpinski_Test < Processing::App
  load_libraries :grammar, :control_panel

  attr_reader :sierpinski, :fine, :coarse, :generation, :scale_factor, :xpos, :ypos

  def setup
    setup_panel
    size(1000, 1000)    
    redraw_fractal
  end
  
  def setup_panel
    control_panel do |c|
      c.title = "Controller:"
      c.slider :fine, 0.00..3.00, 0.00
      c.slider :coarse, 0.00..3.00, 0.00
      c.slider :generation, 5..8, 6
      c.slider :scale_factor, 1..4, 1
      c.slider :xpos, 0..1000, 100
      c.slider :ypos, 0..1000, 700
      c.button :redraw_fractal
    end
  end

  def redraw_fractal
    @sierpinski = Sierpinski.new(xpos, ypos)
    sierpinski.gamma = fine/150 + coarse/15
    sierpinski.factor = scale_factor  
    sierpinski.create_grammar(generation.to_int)
  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, :gamma, :factor
  attr_reader :xpos, :ypos
  DELTA = (Math::PI/180) * 60.0  
  
  def initialize(xpos, ypos)
    @xpos = xpos
    @ypos = ypos
    @axiom = "A"
    @grammar = Grammar.new axiom
    grammar.add_rule "A", "B+A+B"        
    grammar.add_rule "B", "A-B-A"
    @start_length = 400
    @factor = 1 
    stroke 255
    @production = axiom
    @draw_length = start_length
    @gamma = 0

  end

  def render                       # NB using affine transforms here
    translate(xpos, ypos)
    scale(factor)
    production.each_char do |element|
      case element
      when 'B', 'A'                    
        line(0, 0, draw_length, 0)
        translate(draw_length, 0)
      when '+'
        rotate(DELTA)
      when '-'        
        rotate(-(DELTA+gamma))
      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
end  


Click on image to see full size, I have since added a coarse slider (and renamed delta as fine). The fine range here is fairly conservative, you should try that first. Before trying the coarse or use both controls in combination, to get some pretty funky fractals... 
NB requires my custom grammar library see Cesàro fractal in a previous post for code.




Followers

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