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

Monday 30 November 2009

Koch curve using l-systems

Revised as of 16 January 2010 to uses a custom grammar library (see Cesàro fractal in a later post) and various modifications

########################################################
# koch_test.rb uses grammar library
#
# A Koch curve implemented using a
# Lindenmayer System in ruby-processing by Martin Prout
########################################################
require 'koch'

class KochCurve < Processing::App
  load_library :grammar
  attr_reader :koch
  
  def setup
    size 600, 600
    @koch = Koch.new
    koch.create_grammar 4
    no_loop
  end
  
  def draw
    background 0
    koch.render
  end
end

############################
# koch.rb
#
# Koch Curve
###########################
class Koch
  include Processing::Proxy
  
  attr_accessor :axiom, :grammar, :start_length, :theta, :production, :draw_length, :xpos, :ypos
  XPOS = 0     # placeholders for turtle array
  YPOS = 1
  ANGLE = 2 
  DELTA = (Math::PI/180) * 90.0    # convert degrees to radians
  
  def initialize
    @axiom = "F-F-F-F"
    @grammar = Grammar.new axiom
    grammar.add_rule('F', "FF-F-F-F-F-F+F")  
    @start_length = 10
    @theta = 0.0
    @xpos = width * 0.6
    @ypos = height * 0.8
    stroke 255
    @production = axiom
    @draw_length = start_length
  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 *=  0.75**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  




 

Towards a forest, or too many matrix calls

If you wanted to create a 'forest' using the Lindenmayer system, you might want to avoid too much pushing and popping of the picture matrix. This is a simple example; where you will find that you need to preserve the original context if you want to draw a second 'tree', otherwise you will not be quite sure where it will get placed, and it will almost certainly be canted over to some extent. All this pushing and popping has a memory load, and creates a lot of 'garbage' for the jvm to cleanup.


########################################################
# Toward a forest implemented using a
# Lindenmayer System in ruby-processing by Martin Prout
########################################################

require 'fplant'

class Forest_Test < Processing::App
  attr_reader :fplant, :fplant2
  def setup
    size 600, 600
    @fplant = FPlant.new
    @fplant2 = FPlant.new
    fplant.simulate 6
    fplant2.simulate 6
    no_loop
  end
  def draw
    background 0
    push_matrix   # save original context
    translate 200, 550
    fplant.render
    pop_matrix    # restore original context
    translate 350, 570
    fplant2.render
    save_frame "forest.png"
  end
end

############################
# fplant.rb
###########################

require 'jcode'  ## required until jruby supports ruby 1.9

class FPlant
  include Processing::Proxy

  DELTA = (Math::PI/180) * 22.5

  attr_accessor :axiom, :rule, :rule1, :start_length, :production, :draw_length

  def initialize
    @axiom = "X"
    @rule = "FF"
    @rule1 = "F-[[X]+X]+F[+FX]-F"   # note nested matrices
    @start_length = 200
    stroke 0, 255, 0                # ghastly green
    stroke_weight 3
    reset
  end

  def reset                         # initialize or reset variables
    @production = axiom
    @draw_length = start_length
  end

  def iterate prod_, rule_, rule1_
    @draw_length *=  0.5
    new_production = prod_    
    new_production.gsub!('F', rule_)
    new_production.gsub!('X', rule1_)
  end

  def render
    production.each_char do |element|
      case element
      when 'F'                     # NB using affine transforms
        line(0, 0, 0, -draw_length)
        translate(0, -draw_length)
      when '+'
        rotate(+DELTA)  
      when '-'
        rotate(-DELTA)    
      when '['
        push_matrix
      when ']'
        pop_matrix
      when 'X'
      else puts "Grammar not recognized"
      end
    end
  end

  def simulate gen
    gen.times do
      @production = iterate(production, rule, rule1)
    end
  end
end



 


















Here is a possible solution, implement your own stack, and only store what you need to, it must be more efficient (see later post, my ripped square fractal for an even lighter weight implementation)!!!


########################################################
# Toward a forest implemented using a
# Lindenmayer System in ruby-processing by Martin Prout
########################################################

require 'fplant'

class Forest_Test < Processing::App
  attr_reader :fplant, :fplant2
  def setup
    size 600, 600
    @fplant = FPlant.new 200, 550, Math::PI/2
    @fplant2 = FPlant.new 350, 570, Math::PI/2
    fplant.simulate 6
    fplant2.simulate 6
    no_loop
  end

  def draw
    background 0
    fplant.render
    fplant2.render
    save_frame "forest.png"
  end
end

############################
# fplant.rb
###########################

require 'jcode'  ## required until jruby supports ruby 1.9
require 'stack'

class FPlant
  include Processing::Proxy

  DELTA = (Math::PI/180) * 22.5

  attr_accessor :axiom, :rule, :rule1, :start_length, :production, :draw_length, :stack, :xpos, :ypos, :theta

  def initialize x0, y0, angle = 0
    @stack = Stack.new
    @xpos = x0
    @ypos = y0
    @theta = angle
    @axiom = "X"
    @rule = "FF"
    @rule1 = "F-[[X]+X]+F[+FX]-F"   # note nested matrices
    @start_length = 200
    stroke 0, 255, 0                # ghastly green
    stroke_weight 3
    reset
  end

  def reset                         # initialize or reset variables
    @production = axiom
    @draw_length = start_length
  end

  def iterate prod_, rule_, rule1_
    @draw_length *=  0.5
    new_production = prod_     
    new_production.gsub!('F', rule_)
    new_production.gsub!('X', rule1_)
  end

  def render
    production.each_char do |element|
      case element
      when 'F'                     # NB NOT using affine transforms
        line(xpos, ypos, (@xpos += -draw_length * Math.cos(theta)), (@ypos += -draw_length * Math.sin(theta)))     
      when '+'
        @theta += DELTA   
      when '-'
        @theta -= DELTA     
      when '['
        stack.push xpos, ypos, theta # using a simple stack to store individual turtle values
      when ']'
        @xpos, @ypos, @theta = stack.pop
      when 'X'
      else puts "Grammar not recognized"
      end
    end
  end

  def simulate gen
    gen.times do
      @production = iterate(production, rule, rule1)
    end
  end
end

############################
# stack.rb  a lightweight stack utility, tuned for turtles
###########################

class Stack  # a simple turtle stack

  def initialize
   @the_stack = []
  end

  def push(xpos, ypos, angle)
    turtle = [xpos, ypos, angle]
    @the_stack.push turtle
  end

  def pop
    @the_stack.pop
  end

  def count
    @the_stack.length
  end
end




Sunday 29 November 2009

Seaweed using Lindenmayer system on ruby processing

Note that the rules here are essentially the same as the simple plant in my previous post, but here I've reduced the basic angle and added some numbers (repeat factors) to the 'rule'.


########################################################
# A simple seaweed implemented using a
# Lindenmayer System in ruby-processing by Martin Prout
########################################################

require 'wrack'

class Wrack_Test < Processing::App
  attr_reader :wrack
  def setup
    size 600, 600
    @wrack = Wrack.new   
    wrack.simulate 4   
    no_loop
  end
  def draw
    background 0
    translate 200, 550
    wrack.render
    save_frame "wrack_4.png"
  end
end

############################
# wrack.rb
###########################

require 'jcode'  ## required until jruby supports ruby 1.9

class Wrack
  include Processing::Proxy
  DELTA = (Math::PI/180) * 18
 
  attr_accessor :axiom, :rule, :rule1, :start_length, :theta, :production, :draw_length, :xpos, :ypos

  def initialize
    @axiom = "F"
    @rule = "3F[2-F]2F[+F][F]"
    @start_length = 90
    stroke 255
    stroke_weight 4
    reset
  end

  def reset                         # initialize or reset variables
    @production = axiom
    @draw_length = start_length
  end

  def iterate prod_, rule_
    @draw_length *=  0.5
    new_production = prod_       
    new_production.gsub!('F', rule_)
  end

  def render
    repeats = 1
    production.each_char do |element|
      case element
      when 'F'                     # NB using affine transforms
        line(0, 0, 0, -draw_length * repeats)
        translate(0, -draw_length * repeats)
        repeats = 1
      when '+'
        rotate(+DELTA * repeats)     
        repeats = 1
      when '-'
        rotate(-DELTA * repeats)       
        repeats = 1
      when '['
        push_matrix
      when ']'
        pop_matrix
      when '1', '2', '3', '4'  # extend this to '9' if you need to
        repeats = Integer(element)
      else puts "Grammar not recognized"
      end
    end
  end
 
  def simulate gen
    gen.times do
      @production = iterate(production, rule)
    end
  end
end


four iterations



five iterations (might look better green?)
 

A basic example of a plant Lindenmayer systems in ruby processing


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

require 'plant'

class Plant_Test < Processing::App
  attr_reader :plant
  def setup
    size 600, 600
    @plant = Plant.new   
    plant.simulate 4   
    no_loop
  end
  def draw
    background 0
    translate 300, 550
    plant.render
    save_frame "plant_4.png"
  end
end

############################
# plant.rb
###########################

require 'jcode'  ## required until jruby supports ruby 1.9

class Plant
  include Processing::Proxy
  DELTA = (Math::PI/180) * 60
 
  attr_accessor :axiom, :rule, :start_length, :theta, :production, :draw_length, :xpos, :ypos

  def initialize
    @axiom = "F"
    @rule = "F[-F]F[+F][F]"
    @start_length = 250
    stroke 255
    stroke_weight 4
    reset
  end

  def reset                         # initialize or reset variables
    @production = axiom
    @draw_length = start_length
  end

  def iterate prod_, rule_
    @draw_length *=  0.5
    new_production = prod_       
    new_production.gsub!('F', rule_)
  end

  def render
    production.each_char do |element|
      case element
      when 'F'            # NB here I am using affine transforms here, and push_matrix to save context
        line(0, 0, 0, -draw_length)  
        translate(0, -draw_length)  
      when '+'
        rotate(+DELTA)   
      when '-'
        rotate(-DELTA)    
      when '['
        push_matrix
      when ']'
        pop_matrix       # pop_matrix to return to original context
      else puts "Grammar not recognized"
      end
    end
  end
 
  def simulate gen
    gen.times do
      @production = iterate(production, rule)
    end
  end
end



two repetitions




three repetitions


 
four repetitions

Pentagonal Fractal

Here as as far as I know I have created my own pentagonal fractal, key things are the axiom has 5 go forward turn right elements, to go with a pentagonal structure.
The angle is 72 degrees to go with a pentagonal structure. The reduce factor has been calculated for nesting pentagon structures. There is an interesting diversion in shapes with the different number of repetitions. If you want to run other that the three repetitions you will need to adjust the starting position (and size particularly for the four repetitions).

##
# A Pentagonal Fractal created using a
# Lindenmayer System in ruby-processing by Martin Prout
###
require 'pentagonal'

class Pentagonal_Test < Processing::App
  attr_reader :pentagonal
  def setup
    size 800, 800
    @pentagonal = Pentagonal.new
    pentagonal.simulate 3
    no_loop   
  end
  def draw
    background 0
    pentagonal.render
  end
end

############################
# pentagonal.rb here I roll one of my own
###########################

require 'jcode'  ## required until jruby supports ruby 1.9

class Pentagonal
  include Processing::Proxy
  SCALE = (3 - Math.sqrt(5))/2   # approximately 0.382
  DELTA = (Math::PI/180) * 72.0  # convert degrees to radians
  attr_accessor :axiom, :rule, :start_length, :theta, :production, :draw_length, :xpos, :ypos

  def initialize
    @axiom = "F-F-F-F-F"
    @rule = "F+F+F-F-F-F+F+F"      # replace F with this string see iterate function
    @start_length = 220
    @theta = 0.0
    @xpos = width*0.75
    @ypos = height*0.85
    stroke 255
    reset
  end

  def reset                         # initialize or reset variables
    @production = axiom
    @draw_length = start_length
  end

  def iterate prod_, rule_
    @draw_length *=  SCALE
    new_production = prod_    
    new_production.gsub!('F', rule_)

  end

  def render
    production.each_char do |element|
      case element
      when 'F'                     # you could use affine transforms here, I prefer to do the Math
        line(xpos, ypos, (@xpos -= draw_length * Math.cos(theta)), (@ypos += draw_length * Math.sin(theta)))  
      when '+'
        @theta += DELTA        
      when '-'
        @theta -= DELTA        
      else puts "Grammar not recognized"
      end
    end
  end

  def simulate gen
    gen.times do
      @production = iterate(@production, @rule)
    end
  end
end







two repetitions






three repetitions















four repetitions

Saturday 28 November 2009

Minkowski island using L-System in ruby processing

Revised as of 28 December 2009 to use a custom grammar library (see Cesàro fractal in a later post)

#################################################
# minkowski_test.rb
#
# A Minkowski island implemented using a
# Lindenmayer System in ruby-processing by Martin Prout
#################################################
require 'minkowski'
require 'grammar'

class Minkowski_Test < Processing::App
  attr_reader :minkowski

  def setup
    size 800, 800
    @minkowski = Minkowski.new
    minkowski.create_grammar 4
    no_loop
  end

  def draw
    background 0
    minkowski.render
  end
end

#############################
# Minkowski Fractal
#
# minkowski.rb
###########################


class Minkowski
  include Processing::Proxy
  require 'grammar'
  DELTA = (Math::PI/180) * 90.0  # L-System angle increment constant, in radians
  attr_accessor :axiom, :grammar, :start_length, :theta, :production, :draw_length, :xpos, :ypos

  def initialize
    @axiom = 'F+F+F+F'              # just one F for a Minkowski sausage
    @grammar = Grammar.new axiom
    grammar.add_rule 'F', 'F-F+F+FF-F-F+F'        # replace F with this string, see iterate function
    @start_length = 400
    @theta = (Math::PI/180) * 30.0  # initialize angle variable
    @xpos = width * 0.6             # starting x position
    @ypos = height/6                # starting y position
    stroke 0, 255, 0
    stroke_weight 2
    @draw_length = start_length
    @production = axiom
  end

  def next_x
    @xpos -= draw_length * Math.cos(theta)
  end

  # a helper function that sets and returns next position x coordinate
  # you could use affine transforms instead but this avoids that overhead
  def next_y
    @ypos += draw_length * Math.sin(theta)  
  end

  def render
    production.each_char do |element|
      case element
      when 'F'              # draw element and move forward                  
        line(xpos, ypos, next_x, next_y)
      when '+'
        @theta += DELTA          
      when '-'
        @theta -= DELTA          
      else puts "Grammar not recognized"
      end
    end
  end

  ##############################
  # create grammar from axiom and
  # rules (adjust scale)
  ##############################

  def create_grammar(gen = 1)
    @draw_length *=  0.25**gen
    @production = @grammar.generate gen
  end
end



 

Friday 27 November 2009

Sierpinski Fractal Implemented in ruby-processing using an L-System

Revised as of 17 January 2010 uses a custom grammar library (see Cesàro fractal in a later post)


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

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 
  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"
    @grammar = Grammar.new axiom
    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.scan(/./).each do |element|
      case element
      when 'F'                    
        turtle = draw_line(turtle, draw_length)
      when '+'
        turtle[ANGLE] += DELTA   # rotate by + theta if going the affine transform route
      when '-'
        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  



Wednesday 25 November 2009

Snake Kolam Ruby Processing


##
# Lindenmayer System in ruby-processing by Martin Prout
###

require 'snake_kolam'

class Kolam_Test < Processing::App
  load_libraries 'grammar'
  
  attr_reader :snake
  
  def setup
    size 900, 900
    @snake = SnakeKolam.new
    snake.create_grammar 4
    no_loop
  end
  
  def draw
    background 0
    stroke(255)
    snake.render
  end
end

############################  
# snake_kolam.rb using l-systems
############################

class SnakeKolam
  include Processing::Proxy
  
  attr_accessor :axiom, :start_length, :xpos, :ypos, :grammar, :production, :draw_length
  XPOS = 0
  YPOS = 1
  ANGLE = 2
  DELTA = (Math::PI/180) * 90.0 # convert degrees to radians
  
  def initialize
    setup_grammar
    @start_length = 160.0
    @theta = (Math::PI/180) * 90.0 # convert degrees to radians
    @draw_length = start_length
    @draw_length = start_length
    @xpos = width/8
    @ypos = height*0.8
  end

  def setup_grammar
    @axiom = "FX+F+FX+F"
    @grammar = Grammar.new(axiom)
    grammar.add_rule("X", "X-F-F+FX+F+FX-F-F+FX")
    @production = axiom
  end
  
  def render                       # NB not using affine transforms here
    turtle = [xpos, ypos, 0.0]
    production.scan(/./).each do |element|
      case element
      when 'F'                    
        turtle = draw_line(turtle, draw_length)
      when '+'
        turtle[ANGLE] += DELTA   # rotate by + theta if going the affine transform route
      when '-'
        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.6**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  




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