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

Thursday 10 December 2009

Stochastic Plant rules Lindenmayer Systems in ruby processing



















#######################################################
# stochastic_test.rb
#
# Lindenmayer System in ruby-processing by Martin Prout
# Exploring terminals with minimum logic
########################################################

require 'stochastic_plant'

class Stochastic_Test < Processing::App
  attr_reader :stochastic, :stochastic1, :stochastic2
  def setup
    size 1000, 800
    @stochastic = StochasticPlant.new 200, 700
    @stochastic1 = StochasticPlant.new 500, 700
    @stochastic2 = StochasticPlant.new 700, 700
    stochastic.create_grammar 5
    stochastic1.create_grammar 4 # simpler plant
    stochastic2.create_grammar 5
    no_loop
  end

  def draw
    background 0
    stroke(0, 255, 0)
    stroke_width(3)
    stochastic.render
    stochastic1.render
    stochastic2.render
  end
end

############################
# stochastic_plant.rb
# includes option for no pen
# move forward, unused here
###########################
require 'stochastic_grammar'

class StochasticPlant
  include Processing::Proxy

  attr_reader :grammar, :axiom, :draw_length, :theta, :xpos, :ypos, :production, :pen_down

  XPOS = 0     # placeholders for turtle array
  YPOS = 1
  ANGLE = 2

  DELTA = PI/8

  def initialize xpos_, ypos_  
    @draw_length = 350
    @xpos = xpos_
    @ypos = ypos_
    @theta = PI/2                  # this way is up?
    setup_grammar
  end

  def setup_grammar
    @axiom = "F"
    @grammar = StochasticGrammar.new(axiom)          
    grammar.add_rule("F", "F[+F]F[-F]F", 0.1)    
    grammar.add_rule("F", "F[+F]F", 0.45)          
    grammar.add_rule("F", "F[-F]F", 0.45)
    @production = axiom
    @pen_down = false
  end

  def render
    stack = Array.new              # ruby array as the turtle stack
    turtle = [xpos, ypos, theta]   # ruby array as a turtle
    production.scan(/./).each do |element|
      case element
      when 'F'                     # NB NOT using affine transforms
        @pen_down = true
        turtle = draw_line(turtle, draw_length)
      when '+'
        turtle[ANGLE] += DELTA
      when '-'
        turtle[ANGLE] -= DELTA  
      when '['
        stack.push(turtle.dup)    # push a copy of the current turtle to stack
      when ']'
        turtle = stack.pop        # assign current turtle to an instance popped from the stack
      else
        puts "Character '#{element}' is not in grammar"
      end
    end
  end

  def create_grammar gen
    @draw_length *= 0.5**gen
    @production = grammar.generate gen
  end

  private
  ######################################################
  # draws a line using current turtle and length parameters
  # unless pen_down = false (then only move forward)
  # 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) if pen_down
    @pen_down = false
    turtle = [new_xpos, new_ypos, turtle[ANGLE]]
  end
end

########################
# stochastic_grammar.rb
# unweighted rules accepted
# with default weight = 1
# complex stochastic rule
########################
class StochasticGrammar
  PROB = 1
  attr_accessor :axiom, :srules

  def initialize axiom
    @axiom = axiom
    @srules = Hash.new   # rules dictionary (a hash of hashes)
  end

  ######################################################
  # randomly selects a rule (with a weighted probability)
  #####################################################

  def stochastic_rule(rules)
    total = rules.inject(0) do |total, rule_and_weight|
      total += rule_and_weight[PROB]
    end
    srand
    chance = rand * total
    rules.each do |rule, weight|
      return item unless chance > weight
      chance -= weight
    end
    return rule
  end

  def has_rule?(pre)
    @srules.has_key?(pre)
  end

  def add_rule(pre, rule, weight = 1)  # default weighting 1 (can handle non-stochastic rules)
    if (has_rule?(pre)) then           # add to existing hash
      srules[pre].store rule, weight
    else
      temp = Hash.new                  # create a new hash for rule/weights
      temp.store rule, weight          # add to new hash
      srules.store pre, temp           # store new hash with pre key
    end
  end

  def new_production(prod)
    prod.gsub!(/./) do |ch|
      (has_rule?(ch)) ? stochastic_rule(srules[ch]) : ch
    end
  end

  def generate(repeat = 0)
    prod = axiom
    repeat.times do
      prod = new_production(prod)
    end
    return prod
  end
end

2 comments:

  1. how to running this module in open processing? i have insert ruby processing plugin and i use windows 7

    ReplyDelete
  2. Have you installed ruby-processing from? https://github.com/jashkenas/ruby-processing/wiki
    on mac/linux it is simples as:-
    sudo gem install ruby-processing
    Then on command line type:-
    rp5 run stochastic_test.rb
    I've written a commando file so you can do this from jEdit (so you don't need a console)
    http://community.jedit.org/?q=filestore/browse/20

    ReplyDelete

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