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

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

Another control panel example with odd turtle turns


##
# A Spiro fractal implemented using a
# Lindenmayer System in ruby-processing by Martin Prout
###
require 'spiro'

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

  attr_reader :spiro, :repeat, :xpos, :ypos

  def setup
    size 600, 800
    setup_panel
    @xpos = width/3
    @ypos = height/4
    @repeat = 5
    redraw
  end

  def setup_panel
    control_panel do |c|
      c.title = "Control:"
      c.menu(:generation, ['5', '6', '7', '8'], '5') {|m| load_menu_item(m) }
      c.slider(:xpos, 0..600, 200)
      c.slider(:ypos, 0..800, 200)
      c.button(:redraw)
    end
  end

  def redraw
    @spiro = Spiro.new(xpos, ypos)
    spiro.create_grammar(repeat)
  end

  def load_menu_item(item)
    @repeat = item.to_i
    case repeat
    when 7
      @xpos = width/2
      @ypos = height/2
    when 5, 6
      @xpos = width/3
      @ypos = height/4
    else
      @xpos = width/2
      @ypos = height/2
    end
  end

  def draw
    background 0
    spiro.render
  end
end

############################
# spiro.rb
###########################
class Spiro
  include Processing::Proxy

  attr_accessor :axiom, :grammar, :start_length, :theta, :production, :draw_length, :xpos, :ypos
  DELTA = (Math::PI/180) * 251                 # convert degrees to radians
  GAMMA = (Math::PI/180) * 252                 # convert degrees to radians

  def initialize(xpos, ypos)
    @axiom = "F"
    @grammar = Grammar.new axiom
    grammar.add_rule "F", "F-F+F+F-F"                  # replace F rule see grammar library
    @start_length = 100
    @theta = 0.0
    @xpos = xpos
    @ypos = ypos
    stroke 255
    @production = axiom
    @draw_length = start_length
  end

  def render                       # NB using affine transforms here
    translate(xpos, ypos)        
    production.each_char do |element|
      case element
      when 'F'                    
        line(0, 0, draw_length, 0)
        translate(draw_length, 0)
      when '+'
        rotate(DELTA)   # rotate by +DELTA
      when '-'
        rotate(-GAMMA)   # rotate by - GAMMA
      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.6**gen
    @production = grammar.generate gen
  end
end 




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.




Tuesday, 5 January 2010

A Sierpinski Variant using L-Systems and ruby processing

A Sierpinski (triangle) variant after Mark Meyers nodebox l-systems...
for the grammar library see a previous post Cesàro fractal


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

class Sierpinski_Test < Processing::App
  load_libraries 'grammar'

  attr_reader :sierpinski

  def setup
    size 500, 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, :theta, :production, :draw_length, :xpos, :ypos
  DELTA = (Math::PI/180) * 60.0                 # convert degrees to radians
  GAMMA = (Math::PI/180) * 60.25              

  def initialize
    @axiom = "A"
    @grammar = Grammar.new axiom
    grammar.add_rule "A", "B+A+B"        
    grammar.add_rule "B", "A-B-A"  
    @start_length = 450
    @theta = 0.0
    @xpos = width * 0.95
    @ypos = height * 0.8
    stroke 255
    @production = axiom
    @draw_length = start_length
  end

  def render                       # NB not using affine transforms here
    repeats = 1
    production.each_char do |element|
      case element
      when 'B', 'A'                    
        line(xpos, ypos, (@xpos -= draw_length * Math.cos(theta)), (@ypos += draw_length * Math.sin(theta)))
      when '+'
        @theta += DELTA * repeats  
        repeats = 1
      when '-'
        @theta -= GAMMA * repeats  
        repeats = 1
      when '2'
        repeats = 2
      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





Monday, 4 January 2010

An interactive l-system sketch using the Control Panel

Requires my grammar library see Cesàro fractal, cycles through 2, 3 and 4 generation versions using the control panel selector (and redraw button).

#####################
#  pentagonal_test.rb
#  by Martin Prout
#####################
require 'pentagonal'

class Pentagonal_Test < Processing::App
  load_library :control_panel
  load_library :grammar
  
  attr_reader :pentagonal, :repeat
  
  def setup
    size 600, 600
    setup_panel    
    @pentagonal = Pentagonal.new
    pentagonal.create_grammar(2)
  end
  
  def setup_panel
    control_panel do |c|
      c.title = "Control:"
      c.menu(:generation, ['2', '3', '4', '5'], '2') {|m| load_menu_item(m) }
      c.button(:redraw)
    end
  end
  
  def draw
    background(0)
    stroke(255)
    pentagonal.render
  end
  
  def redraw
    @pentagonal = Pentagonal.new
    pentagonal.create_grammar(repeat)
  end
  
  def load_menu_item(item)
    @repeat = item.to_i
  end
end


############################
# pentagonal.rb
# Pentagonal Fractal
###########################

class Pentagonal
  include Processing::Proxy

  SCALE = 0.44  
  DELTA = (Math::PI/180) * 36.0  # convert degrees to radians
  XPOS = 0     # placeholders for turtle array
  YPOS = 1
  ANGLE = 2 
  attr_accessor :axiom, :grammar, :start_length, :theta, :production, :draw_length, :xpos, :ypos

  def initialize
    setup_grammar
    @start_length = 100
    @theta = 0.0
    @xpos = width*0.5
    @ypos = height*0.7    
    @draw_length = start_length
  end
  
  def setup_grammar
    @axiom = "F--F--F--F--F"
    @grammar = Grammar.new axiom
    grammar.add_rule "F", "F+F+F-F-F-F+F+F+"   # replace F with this string see iterate function
    @production = axiom
  end

  def render
    turtle = [xpos, ypos, 0.0]    # simple array act as turtle
    production.scan(/./).each do |element|
      case element
      when 'F'                     # NB NOT using affine transforms
        turtle = draw_line(turtle, draw_length)
      when '+'
        turtle[ANGLE] += DELTA  
      when '-'
        turtle[ANGLE] -= DELTA      
      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 *= SCALE**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  



 

Saturday, 2 January 2010

Checking the probabilities

After a bit of a faux pas, where I needed to change my code I thought I'd better check the randomness of the code (its so easy in ruby).

class ProbTest
  attr_accessor :rules
 
  def initialize
    @rules = Hash.new
    rules.store(["abc", 0], 0.1)
    rules.store(["def", 0], 0.2)
    rules.store(["ghi", 0], 0.3)
    rules.store(["jkl", 0], 0.4)
  end
 
  def stochastic_rule(rules)
    target = rand
    rules.each do |item, weight|
      return item unless target > weight
      target -= weight
    end
  end
 
  def increment(rules)
    item = stochastic_rule(rules)
    item[1] += 1
    return item
  end 
end

test = ProbTest.new
10000.times do
  test.increment(test.rules)
end

puts test.rules

# {["abc", 1002]=>0.1, ["def", 2043]=>0.2, ["ghi", 2980]=>0.3, ["jkl", 3975]=>0.4}


Such a simple check would not satisfy a statistician, but there good enough for me... to see it working in my December 10th post.

Monday, 28 December 2009

Peano curve fractal using Lindenmayer Systems

Peano curve using my custom library

######################################################
# peano_test.rb
#
# Lindenmayer System in ruby-processing by Martin Prout
######################################################
require 'grammar'
require 'peano'

class Peano_Test < Processing::App
  attr_reader :peano
  def setup
    size 1000, 1000
    @peano = Peano.new
    peano.create_grammar 4
    no_loop
  end
  def draw
    background 0
    peano.render
  end
end

############################
# peano.rb
#
# Peano Fractal
###########################
class Peano
  include Processing::Proxy

  require 'grammar'
  DELTA = (Math::PI/180) * 60.0   # convert degrees to radians
  attr_accessor :axiom, :grammar, :start_length, :theta, :production, :xpos, :ypos, :draw_length

  def initialize
    @axiom = "XF"
    @grammar = Grammar.new axiom
    grammar.add_rule 'X', "X+YF++YF-FX--FXFX-YF+"    ## replace X with this string see grammar library
    grammar.add_rule 'Y', "-FX+YFYF++YF+FX--FX-Y"   ## replace Y with this string see grammar library
    @start_length = 120.0
    @theta = 0.0
    @xpos = width/3
    @ypos = height/10
    @production = axiom
    @draw_length = start_length
  end

  def render()
    @production.each_char do |element|
      case element
      when 'F'
        line(@xpos, @ypos, (@xpos -= (draw_length * Math.cos(theta))), (@ypos += (draw_length * Math.sin(theta))))
      when '+'
        @theta += DELTA
      when '-'
        @theta -= DELTA
      when 'X','Y'     ## do nothing except recognize the grammar
      else puts "Grammar not recognized"
      end
    end
  end

  def create_grammar(gen)
    stroke 255
    @draw_length *=  0.6**gen
    @production = grammar.generate gen
  end
end



 

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