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

Sunday 31 January 2010

Hello Peasy in ruby processing (a simple PeasyCam example)

load_libraries 'PeasyCam'
import 'peasy'

attr_reader :cam

def setup()
  size(200,200,P3D)
  configure_camera()
end

def configure_camera()
  @cam = PeasyCam.new(self, 100)
  cam.set_minimum_distance(50)
  cam.set_maximum_distance(500)
end

def draw()
  rotate_x(-0.5)
  rotate_y(-0.5)
  background(0)
  fill(255, 0, 0)
  box(30)
  push_matrix()
  translate(0, 0, 20)
  fill(0, 0, 255)
  box(5)
  pop_matrix()
end








Saturday 30 January 2010

Using the PeasyCam library in ruby-processing

Here is good example of how nice it is to have libraries, the PeasyCam library makes configuring the camera a cinch (it also works pretty smoothly). The lsystem grammar library means you can forget about how the production string is generated. What was the question?


########################################################
# A 3D Hilbert fractal implemented using a
# Lindenmayer System in ruby-processing by Martin Prout
# Best if you've got opengl
########################################################

require 'hilbert'

class Hilbert_Test < Processing::App
  full_screen # NB: All distances are relative to screen height
  load_libraries 'grammar', 'PeasyCam', 'opengl'
  import 'peasy' 
  import "processing.opengl" if library_loaded? "opengl"  
  attr_reader :hilbert, :cam
  def setup()
    library_loaded?(:opengl) ? configure_opengl : render_mode(P3D)
    configure_peasycam
    @hilbert = Hilbert.new(height)
    hilbert.create_grammar(3)
    no_stroke()
  end
  
  def configure_peasycam
    cam = PeasyCam.new(self, height/10)  
    cam.set_minimum_distance(height/10)
    cam.set_maximum_distance(height/2)
  end
  
  def configure_opengl
    render_mode OPENGL
    hint ENABLE_OPENGL_4X_SMOOTH     # optional
    hint DISABLE_OPENGL_ERROR_REPORT # optional
  end

  def draw()
    background(0)
    lights()
    hilbert.render()
  end
end

############################
# hilbert.rb
###########################
class Hilbert
  include Processing::Proxy

  attr_reader :grammar, :axiom, :production, :premis, :rule,
  :theta, :scale_factor, :distance, :phi, :len
  
  def initialize(len)
    @axiom = "X"
    @grammar = Grammar.new(axiom)
    @production = axiom
    @premis = "X"
    @rule = "^<XF^<XFX-F^>>XFX&F+>>XFX-F>X->"
    @len = len
    @distance = len/12      # distance value relative to screen height
    @theta = Math::PI/180 * 90
    @phi = Math::PI/180 * 90
    grammar.add_rule(premis, rule)
    no_stroke()
  end

  def render()
    translate(-len/42, len/42, -len/42)  # use the "answer?" to center the Hilbert
    fill(0, 75, 152)
    light_specular(204, 204, 204)
    specular(255, 255, 255)
    shininess(1.0)
    production.scan(/./) do |ch
      case(ch)
      when "F"    
        translate(0, distance/-2, 0)
        box(distance/9 , distance, distance/9)
        translate(0, distance/-2, 0)
      when "+"
        rotateX(-theta)
      when "-"
        rotateX(theta)
      when ">"
        rotateY(theta)
      when "<"
        rotateY(-theta)
      when "&"
        rotateZ(-phi)
      when "^"
        rotateZ(phi)
      when "X"
      else
        puts("character '#{ch}' not in grammar")
      end
    end
  end
  ##############################
  # create grammar from axiom and
  # rules (adjust scale)
  ##############################
  
  def create_grammar(gen)
    @distance *= 0.5**gen
    @production = @grammar.generate gen
  end
end



Friday 29 January 2010

3D Hilbert Using LSystems and ruby-processing

Here is a 3D Hilbert in ruby-processing, you can see the basic shape if you set the number of repeats to 1. For me the best look was 3 iterations (starts to look a bit fuzzy with 4 repeats).
I used this application to test my grammar (and lsystem vanilla processing) library on 32 bit Windows (XP). Which worked OK for me, because linux has a bit of an issue about OPENGL with ruby-processing unless fullscreen (vanilla processing works s fine) I have used P3D here.
I'm sure it could work a bit better using opengl. It would be cool to use my vanilla library with ruby-processing but I can't find an interface for the java char in ruby....


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

require 'hilbert'

class Hilbert_Test < Processing::App
  load_libraries :grammar, :control_panel
  attr_reader :hilbert, :y, :x
  attr_accessor :x_view, :y_view, :zoom, :repeat
  def setup()
    size(500, 500, P3D)
    setup_panel
    @x_view = 400.0;
    @y_view = 0.0;
    @zoom = 9.0;
    @hilbert = Hilbert.new
    hilbert.create_grammar(3)
    no_stroke()
    camera(x_view, y_view, zoom, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)
    translate(width / 2, height / 2)
  end

 def setup_panel
   control_panel do |c|
     c.title = "Control:"
     c.menu(:repeat, ['1', '2', '3', '4'], '3') {|m| load_menu_item(m) }
     c.slider(:zoom, -100..400, zoom)
     c.slider(:x_view, 300..700, x_view)
     c.slider(:y_view, -400..400, y_view)
     c.button :reset!
   end
 end

 def reset!
   x_view = 0.0
   y_view = 0.0
   zoom = 5
   @hilbert = Hilbert.new
   hilbert.create_grammar(repeat.to_i)
 end

 def load_menu_item(item)
   repeat = item
 end


  def draw()
    background(0)
    hilbert.render()
    ambient_light(0, 255, 0)
    directional_light(0, 255, 0, 0.0, -1.0, 0.0)
    camera(x_view, y_view, zoom, # eyeX, eyeY, eyeZ
  0.0, 0.0, 0.0,           # centerX, centerY, centerZ
  0.0, 1.0, 0.0)
  end
end

############################
# hilbert.rb
###########################
class Hilbert
  include Processing::Proxy

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

  def initialize()
    @axiom = "X"
    @grammar = Grammar.new(axiom)
    @production = axiom
    @premis = "X"
    @rule = "^<XF^<XFX-F^>>XFX&F+>>XFX-F>X->"
    @distance = 160
    @theta = Math::PI/180 * 90
    @phi = Math::PI/180 * 90
    grammar.add_rule(premis, rule)
    no_stroke()
  end

  def render()
    production.scan(/./) do |ch|
      case(ch)
      when "F"  
        fill(0, 200, 0)
        translate(0, distance/-2, 0)
        box(distance/9 , distance, distance/9)
        translate(0, distance/-2, 0)
      when "+"
        rotateX(-theta)
      when "-"
        rotateX(theta)
      when ">"
        rotateY(theta)
      when "<"
        rotateY(-theta)
      when "&"
        rotateZ(-phi)
      when "^"
        rotateZ(phi)
      when "X"
      else
        puts("character '#{ch}' not in grammar")
      end
    end
  end
  ##############################
  # create grammar from axiom and
  # rules (adjust scale)
  ##############################

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






Thursday 28 January 2010

Two dimensional Hilbert fractal with LSystems and ruby-processing


#############################################################
# A Hilbert fractal implemented (uses my grammar library)
# Lindenmayer System in ruby-processing by Martin Prout
#############################################################

require 'hilbert'

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


############################
# hilbert.rb
###########################
class Hilbert
  include Processing::Proxy

  attr_accessor :grammar, :axiom, :draw_length, :turtle
  DELTA = Math::PI/2
  XPOS = 0  # placeholders for turtle
  YPOS = 1
  THETA = 2
  def initialize 
    @axiom = "FL"
    @grammar = Grammar.new axiom
    grammar.add_rule "L", "+RF-LFL-FR+"
    grammar.add_rule "R", "-LF+RFR+FL-"
    @draw_length = 200
    stroke 0, 255, 0
    stroke_weight 2 
    @turtle = Array.new(3)           # using an array as turtle
    turtle[XPOS] = width/9
    turtle[YPOS] = height/9
    turtle[THETA] = 0
  end

  def render
    axiom.scan(/./) do |element|
      case element
      when 'F'                      # NB NOT using affine transforms
        draw_line(turtle)
      when '+'
        @turtle[THETA] += DELTA    
      when '-'
        @turtle[THETA] -= DELTA    
      when 'L'
      when 'R'
      else puts "Grammar not recognized"
      end
    end
  end
 
  def draw_line(turtle)
    x_temp = turtle[XPOS]
    y_temp = turtle[YPOS]
    @turtle[XPOS] += draw_length * Math.cos(turtle[THETA])
    @turtle[YPOS] += draw_length * Math.sin(turtle[THETA])
    line(x_temp, y_temp, turtle[XPOS], turtle[YPOS])
  end

  ##############################
  # create grammar from axiom and
  # rules (adjust scale)
  ##############################
 
  def create_grammar(gen)
    @draw_length *=  0.6**gen
    @production = @grammar.generate gen
  end
end





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 

Saturday 16 January 2010

Islands in ruby processing using my grammar library

See Cesàro fractal for grammar.rb library

require 'island'

class Test_Island < Processing::App
  load_libraries "grammar"

  attr_reader :island

  def setup
    size 500, 500
    stroke 255
    smooth
    @island = Island.new
    island.create_grammar 2
    no_loop  
  end

  def draw
    background 0
    island.render  
  end
end

######################################################
# island.rb
######################################################
class Island
  include Processing::Proxy

  attr_reader :axiom, :grammar, :start_length, :theta, :production, :draw_length,
    :repeats, :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", "F+f-FF+F+FF+Ff+FF-f+FF-F-FF-Ff-FFF")
    grammar.add_rule("f", "ffffff")
    @start_length = 1200.0
    @theta = 0
    @xpos = width/4
    @ypos = height*0.8
    @production = axiom
    @draw_length = start_length
  end

  ##############################################################################
  # Not strictly in the spirit of either processing in my render
  # function I have ignored the processing translate/rotate functions in favour
  # of the direct calculation of the new x and y positions, thus avoiding such
  # affine transformations.
  ##############################################################################

  def render()
    turtle = [xpos, ypos, theta]                # simple array for turtle
    production.scan(/./).each do |element|
      case element
      when 'F'
        turtle = draw_line(turtle, draw_length)
      when 'f'
        turtle = forward(turtle, draw_length/2)
      when '+'
        turtle[ANGLE] += DELTA
      when '-'
        turtle[ANGLE] -= DELTA
      else puts "Character '#{element}' not in grammar"
      end
    end
  end

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

  def create_grammar(gen)
    @draw_length *= 0.08**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

  def forward(turtle, length)
    new_xpos = turtle[XPOS] + length * Math.cos(turtle[ANGLE])
    new_ypos = turtle[YPOS] + length * Math.sin(turtle[ANGLE])
    turtle = [new_xpos, new_ypos, turtle[ANGLE]]
  end
end





Thursday 14 January 2010

Refactoring my Penrose Snowflake

Dan Mayer over at devver.net has kindly analysed and re-factored my penrose_snowflake with caliper (something I've never heard of but then I'm not really in the community, just ploughing my own furrow). He also extended my code in an interesting way to produce a "snowflake screensaver" which labours a bit.
Well I tried out Dans refactoring on my own code (which I had already changed to have a separate grammar library).  Here is my revised penrose_snowflake.rb (requires grammar.rb and runs with penrose.rb) incorporating some additional refactoring, which you may or may not like depending on your view of the ternary operator (my multiplier has fewer lines of code and is 4% faster according to my benchmark test). However I have since come up with a different version which I have now used to replace my original posting!!! I think the new version will come into its own for my bracketed L-Systems and my Island fractal which is next on the list for refactoring.



class PenroseSnowflake
  include Processing::Proxy

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

  def initialize
    @axiom = "F3-F3-F3-F3-F"
    @grammar = Grammar.new axiom
    grammar.add_rule "F", "F3-F3-F45-F++F3-F"
    @start_length = 450.0
    @theta = 0
    @xpos = width * 0.8
    @ypos = height * 0.95
    @production = axiom
    @draw_length = start_length
  end

  ##############################################################################
  # Not strictly in the spirit of either processing or L-Systems in my iterate
  # function I have ignored the processing translate/rotate functions in favour
  # of the direct calculation of the new x and y positions, thus avoiding such
  # affine transformations.
  ##############################################################################

  def render()
    repeats = 1
    production.each_char do |element|
      case element
      when 'F'
        line(xpos, ypos, (@xpos -= multiplier(repeats, :cos)), (@ypos += multiplier(repeats, :sin)))
        repeats = 1
      when '+'
        @theta += DELTA * repeats
        repeats = 1
      when '-'
        @theta -= DELTA * repeats
        repeats = 1
      when '3', '4', '5'
        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.4**gen
    @production = grammar.generate gen
  end

  def multiplier(repeats, type)
    value = draw_length * repeats
    (type == :cos)?  value * Math.cos(theta) : value *  Math.sin(theta)
  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