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

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



 

Saturday, 19 December 2009

3D Lindenmayer Systems using the anar+ library with ruby-processing

When you start this sketch one block is drawn, by repeatedly pressing the space-bar you create a new iteration, and more blocks are drawn (don't go mad or at least ensure there is enough memory allocated/available) scrolling zooms you in and out and you eventually you can kind-off step through the plane off the screen. Hold down the middle mouse button to rotate the plane of the image (prepare to be amazed, at what a little script can do).


load_libraries 'anar', 'opengl'
import "anar" if library_loaded? "anar"
import "lsys.Grammar" if library_loaded? "anar"
import "processing.opengl" if library_loaded? "opengl"

full_screen

attr_accessor :my_obj, :grammar, :box, :ts, :tf, :init_t, :r, :s

def setup()
  library_loaded?(:opengl) ? setup_opengl : fail_opengl
  hint(ENABLE_OPENGL_4X_SMOOTH)
  Anar.init(self)
  Anar.draw_axis(true)
  init_grammar()
  interpret_init()
end

def init_grammar()
  @grammar = Grammar.new("bt")
  # here define the rules
  # * means any kind of symbol
  # the example rules below are therefore non contextual
  grammar.add_rule("*b*", "brb")
  grammar.add_rule("*t*", "rssb")
  grammar.add_rule("*r*", "rs")
  grammar.add_rule("*s*", "s")
  # this one makes it context dependant
  #     grammar.add_rule("sss", "ss")
  puts(grammar)
end


def interpret_init
  # base element
  @box = Box.new(10, 10, 50)
  # initial position
  @init_t = Translate.new(0, 50, 0)
  # base transformations
  @ts = Translate.new(Anar.Pt(0, 10, -0.001))
  @r = RotateZ.new(0.5)
  @s = Scale.new(Anar.Pt(0.99, 0.99, 0.99))
  interpret_grammar()          
end

def interpret_grammar()
  @my_obj = Obj.new
  @tf = Transform.new(init_t)

  grammar.num_of_symbols.times do |i|

    case(grammar.symbol(i)[0].chr) # todo this is bit of hack, I need understand grammar better
      when 'b'
        my_copy = Obj.new(box, tf)
        my_obj.add(my_copy)
        @tf = Transform.new(tf)   
      when 't'
        tf.apply(ts)   
      when 'r'
        tf.apply(r)                                            
      when 's'
        tf.apply(s)                                            
      else
        puts "grammar not understood"
    end
  end
  Anar.cam_target(my_obj)
end

def draw()
  background(153)
  my_obj.draw()
end


def key_pressed()
  if(key==' ')
    grammar.step()
    interpret_grammar()
    puts(grammar)
  end
  if(key=='r')
    grammar.reset()
    interpret_init()
    interpret_grammar()
    puts(grammar)
  end
    if(key=='p')
    save_frame("block.png")
  end
end

def setup_opengl
  render_mode OPENGL
  hint ENABLE_OPENGL_4X_SMOOTH #optional
end
              

def fail_opengl
  abort "!!!You absolutely need opengl for this sketch!!!"
end



Friday, 18 December 2009

Another Example of Using Anar+ lib in ruby-processing

NB you absolutely must try rotating the plane of the image (hold central mouse button or equivalent down), and zoom into the shape/building area...


load_libraries 'anar', 'opengl'
import "anar" if library_loaded? "anar"
import "processing.opengl" if library_loaded? "opengl"

attr_accessor :my_obj, :group
full_screen

def setup()
  library_loaded?(:opengl) ? setup_opengl : fail_opengl
  Anar.init(self)
  Anar.draw_axis()
  Face.globalRender = RenderFaceNormal.new(OogColor.new(255, 100), OogColor.new(100))
  init_form()
end

def init_form()
  @my_obj = Obj.new
  @group = Group.new

  ###################
  #CONE
  ###################
  cone = Cone.new(50,100,20)
  cone.set("cone")
  cone.translate(100,100,0)

  puts(cone.to_obj_exporter())
  group.add(cone)

  ###################
  #BOX
  ###################
  box = Box.new(10,20,30)
  box.set("box")
  box.rotate_z(0)
  box.rotate_x(0)
  box.translate(100, 0, 0)
  puts(box.to_obj_exporter("box"))
  puts(box.parent_list(-1))
  group.add(box)

  ###################
  #CYLINDER
  ###################
  cylinder = Cylinder.new(50, 24, 50)
  cylinder.set("cylinder")
  cylinder.translate(-100, 0, 0)
  puts(cylinder.to_obj_exporter("cylinder"))
  group.add(cylinder)

  ###################
  #ELLIPSE
  ###################
  ellipse = Ellipse.new(40, 20)
  ellipse.set("ellipse")
  puts(ellipse.to_obj_exporter("ellipse"))
  group.add(ellipse)

  ###################
  #SWISSCROSS3D
  ###################
  swissCross3D = SwissCross3D.new(10, 10)
  swissCross3D.set("swissCross3D")
  #swissCross3D.fill(255,0,0,200)
  puts(swissCross3D.to_obj_exporter("swissCross3D"))
  #swissCross3D.translate(-100,0,0)
  group.add(swissCross3D)

  ###################
  #REVOLVER
  ###################
  ctrlRevol = Pts.new()
  ctrlRevol.add(Anar.Pt(30,0,30))
  ctrlRevol.add(Anar.Pt(10,0,40))
  ctrlRevol.add(Anar.Pt(20,0,60))
  ctrlRevol.add(Anar.Pt(20,0,70))
  revolver = Revolve.new(ctrlRevol, Anar.Pt(0,0,20), 12
  revolver.set("revolver")
  puts(revolver.to_obj_exporter("revolver"))
  group.add(revolver)

  ##################
  ###################
  my_obj.add(box)
  my_obj.add(cone)
  my_obj.add(cylinder)
  my_obj.add(ellipse)
  my_obj.add(swissCross3D)
  my_obj.add(revolver)
  Anar.sliders(swissCross3D)
  Anar.sliders(revolver)
  Anar.cam_target(revolver)
end

def fail_opengl
  abort "!!!You absolutely need opengl for this sketch!!!"
end

def setup_opengl
  render_mode OPENGL
  hint ENABLE_OPENGL_4X_SMOOTH #optional
end

def draw()
  background(155)
  group.draw()
end

def key_pressed()
    if(key==' ') then
      init_form
    end
end



 

Thursday, 17 December 2009

Using the Anar+ library in ruby-processing

First you need to put both opengl and anar libraries in eponymous folders (same name) and them both in an outer folder library, then your sketch can load the libraries 'easy-peasy'. If you are not on linux, you do not need not run full screen but you should because its great. This sketch apart from the full_screen is virtually a straight translation of a java processing example from http://anar.ch. NB Use scroll wheel to zoom or equivalent gizmo.


load_libraries 'anar', 'opengl'
import "anar" if library_loaded? "anar"
import "processing.opengl" if library_loaded? "opengl"

attr_accessor :my_obj
full_screen

def setup()
  library_loaded?(:opengl) ? setup_opengl : fail_opengl
  frame_rate(200)

  Anar.init(self)
  Anar.draw_axis(true)

  init_form()
end

def setup_opengl
  render_mode OPENGL
  hint ENABLE_OPENGL_4X_SMOOTH #optional
end

def init_form()

  #Create a new Line
  pts = Pts.new

  pts.add(Anar.Pt(100,110,20))
  pts.add(Anar.Pt(110,100,40))
  pts.add(Anar.Pt(110,90,60))
  pts.add(Anar.Pt(90,90,90))
  #Create a Face
  f = Star.new(50,100,5)
  #Extrude the face along the Line
  @my_obj = Extrude.new(f,pts)
  Anar.cam_target(my_obj)
  Anar.sliders(my_obj)
end

def draw()
  background(155)
  my_obj.draw()
end

def fail_opengl
  abort "!!!You absolutely need opengl for this sketch!!!"
end

#Toggle the display of the sliders
def keyPressed()
  Anar.sliders_toggle()
end

























Original author Guillaume LaBelle (I think?), sliders are bit tiny and fall off the end but functionality is superb (there might have been sound as there is some complaint about lack of promidi, however nothing happened for me when I loaded the appropriate libraries).

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

Tuesday, 8 December 2009

A square edge fractal with L-System and ruby-processing

LSystem using my custom grammar library (see Cesàro fractal in earlier post)


########################################################
# Chequer implemented using a grammar library
# Lindenmayer System in ruby-processing by Martin Prout
########################################################

require 'chequer'

class Chequer_Test < Processing::App
  load_libraries 'grammar'

  attr_reader :chequer

  def setup
    size 600, 600
    @chequer = Chequer.new  
    chequer.create_grammar 4  
    no_loop
  end

  def draw
    background 0
    chequer.render
  end
end

############################
# chequer.rb
###########################

class Chequer
  include Processing::Proxy

  attr_accessor :axiom, :grammar, :production, :draw_length, :theta, :xpos, :ypos
  DELTA = Math::PI/2

  def initialize
    @axiom = "F-F-F-F"
    @grammar = Grammar.new axiom
    @grammar.add_rule "F", "FF-F-F-F-FF"
    @draw_length = 500
    stroke 0, 255, 0
    stroke_weight 2  
    @xpos = width * 0.9
    @ypos = height/10
    @theta = 0
  end

  def render
    stack = Array.new               # directly using locally defined array as turtle stack
    production.each_char do |element|
      case element
      when 'F'                     # NB NOT using affine transforms
        x_temp = xpos
        y_temp = ypos
        @xpos -= draw_length * Math.cos(theta) # looks neater here
        @ypos -= draw_length * Math.sin(theta)
        line(x_temp, y_temp, xpos, ypos)
      when '+'
        @theta += DELTA
      when '-'
        @theta -= 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 /=  3**gen
    @production = @grammar.generate gen
  end
end




Monday, 7 December 2009

Seasonal sketch, adding terminals to L-System plant

Revised version as of 10th January 2010 now uses my grammar library.


########################################################
# Lindenmayer System in ruby-processing by Martin Prout
# Exploring terminals with minimum logic (Seasonal)
########################################################

require 'seasonal'

class Seasonal_Test < Processing::App
  load_library :grammar
  
  attr_reader :seasonal
  
  def setup
    size 600, 800
    @seasonal = Seasonal.new
    seasonal.create_grammar(3)
    no_loop
  end
  
  def draw
    background 0
    seasonal.render
  end
end

############################
# seasonal.rb
###########################


class Seasonal  
  include Processing::Proxy 
  
  attr_reader :grammar, :axiom, :draw_length, :theta, :xpos, :ypos, :production
  DELTA = Math::PI/4
  
  def initialize    
    @axiom = "F"
    @grammar = Grammar.new axiom
    grammar.add_rule("F", "F[-F]F[+F][F]")
    @draw_length = 600
    @xpos = width/2
    @ypos = height * 0.9
    @theta = Math::PI/2  # this way is up?    
  end

  def render
    stack = Array.new               # directly using locally defined array as turtle stack
    production.each_char do |element|
      case element
      when 'F'                     # NB NOT using affine transforms
        x_temp = xpos
        y_temp = ypos
        @xpos += draw_length * Math.cos(theta) * 0.5 # looks neater here
        @ypos -= draw_length * Math.sin(theta) * 0.5
        stroke 0, 255, 0
        stroke_weight 4    
        line(x_temp, y_temp, xpos, ypos)
      when '+'
        @theta += DELTA      
      when '-'
        @theta -= DELTA      
      when '['
        stack.push([xpos, ypos, theta])
      when ']'
        no_stroke
        fill 255, 0, 0
        # add some seasonal red berries as terminals using minimal logic
        ellipse(xpos, ypos, draw_length/3, draw_length/3) unless stack.length < 2 
        @xpos, @ypos, @theta = *stack.pop
      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



A Cesàro (or torn square) fractal in ruby processing (using a grammar library)

Includes code for grammar library...
To use the library as a local library put grammar.rb in a folder grammar, and put that folder
in a library folder along with the ruby code (otherwise place it with the other libraries in the distribution, in my test local libraries get called first).


#############################################################
# A Cesaro fractal implemented (uses a grammar.rb library)
# Lindenmayer System in ruby-processing by Martin Prout
#############################################################
require 'cesaro'

class Cesaro_Test < Processing::App
  load_library :grammar
  
  attr_reader :cesaro
  
  def setup
    size 600, 600
    @cesaro = Cesaro.new  
    cesaro.create_grammar 5  
    no_loop
  end
  
  def draw
    background 0
    cesaro.render
  end
end

############################
# cesaro.rb
###########################
class Cesaro
  include Processing::Proxy
  
  attr_reader :grammar, :axiom, :draw_length, :theta, :xpos, :ypos, :production
  
  XPOS = 0     # placeholders for turtle array
  YPOS = 1
  ANGLE = 2 
  DELTA = Math::PI/16
  
  def initialize  
    setup_grammar
    @draw_length = 200
    stroke 0, 255, 0
    stroke_weight 2  
    @xpos = width/7
    @ypos = height/7
    @theta = Math::PI/2  # this way is up?
  end
  
  def setup_grammar
    @axiom = "F7-F7-F7-F"
    @grammar = Grammar.new axiom
    grammar.add_rule "F", "F6-F67+F6-F" 
    @production = axiom
  end

  def render
    repeats = 1
    stack = Array.new     # directly using a locally defined array as the turtle stack
    turtle = [xpos, ypos, theta]    # simple array for turtle
    production.scan(/./).each do |element|
      case element
      when 'F'                     # NB NOT using affine transforms
        turtle = draw_line(turtle, draw_length, repeats)
        repeats = 1
      when '+'
        turtle[ANGLE] += DELTA * repeats    
        repeats = 1
      when '-'
        turtle[ANGLE] -= DELTA * repeats      
        repeats = 1
      when '6', '7'   # NB '6' means seven repeats etc.
        repeats += Integer(element)
      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, repeats)
    length *= repeats
    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 grammar
# library with unique premise/rules
############################
class Grammar
  attr_accessor :axiom, :rules
  
  def initialize axiom
    @axiom = axiom
    @rules = Hash.new
  end
  
  def add_rule premise, rule
    rules.store(premise, rule)
  end
  
  ##########################################
  # replace each pre char with a unique rule
  ##########################################
  def new_production production
    production.gsub!(/./) { |c| (r = @rules[c]) ? r : c }
  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  
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