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

Sunday, 22 November 2009

Penrose Snowflake L-Systems using ruby-processing

Revised as of 15 January, now uses a custom grammar library (see Cesàro fractal in a later post).  If you landed here from http://devver.net/blog/2009/12/playing-with-processing-making-snow/
this is not the code Dan re-factored, and I have come up with some refactoring of my own which I hope is clearer than my original code.

##
# Lindenmayer System in ruby-processing by Martin Prout
# Loosely based on a processing Penrose L-System
# by Geraldine Sarmiento
###

require 'penrose_snowflake'

class Penrose < Processing::App 
  load_libraries "grammar"
  
  attr_reader :penrose
  
  def setup
    size 1000, 900
    stroke 255
    smooth
    @penrose = PenroseSnowflake.new
    penrose.create_grammar 4
    no_loop    
  end
  
  def draw
    background 0
    penrose.render
  end
end

###################################
# penrose_snowflake.rb
###################################
class PenroseSnowflake
  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) * 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 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()
    repeats = 1
    turtle = [xpos, ypos, theta]                # simple array for turtle
    production.scan(/./).each do |element|
      case element
      when 'F'
        turtle = draw_line(turtle, draw_length, repeats)
        repeats = 1
      when '+'
        turtle[ANGLE] += DELTA * repeats
        repeats = 1
      when '-'
        turtle[ANGLE] -= DELTA * repeats
        repeats = 1
      when '3', '4', '5'
        repeats += Integer(element)
      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.4**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 = 1)
    new_xpos = turtle[XPOS] - repeats * length * Math.cos(turtle[ANGLE])
    new_ypos = turtle[YPOS] + repeats * length * Math.sin(turtle[ANGLE])
    line(turtle[XPOS], turtle[YPOS], new_xpos, new_ypos)
    turtle = [new_xpos, new_ypos, turtle[ANGLE]]
  end 
end



Dragon Fractal Using L-Systems and ruby-processing

Revised as of 27 December 2009 to use my custom grammar library. See Cesàro fractal in a later post for code.

##
# Lindenmayer System in ruby-processing by Martin Prout
###
require 'dragon'

class Dragon_Test < Processing::App
  load_libraries 'grammar'
  
  attr_reader :dragon
  
  def setup
    size 600, 480
    @dragon = Dragon.new
    dragon.create_grammar 10 
    no_loop
  end
  
  def draw
    background 0
    dragon.render
  end
end

############################
# dragon.rb
# Dragon Fractal
###########################

class Dragon
  include Processing::Proxy
  
  attr_accessor :axiom, :grammar, :start_length, :theta, :production, :draw_length

  def initialize
    @axiom = "FX"
    @grammar = Grammar.new(axiom)
    grammar.add_rule "X", "+FX--FY+"   # replace X rule see 'grammar' library
    grammar.add_rule "Y", "-FX++FY-"   # replace Y rule see 'grammar' library
    @start_length = 240.0
    @draw_length = start_length
    @theta = (Math::PI/180) * 45.0     # convert degrees to radians
    @production = axiom
    stroke 255
  end

  def render                           # using affine transforms
    translate(width/3, height/3)
    rotate theta * 2
    production.each_char do |element|
      case element
      when 'F'
        line(0, 0, 0, -draw_length)      
        translate(0, -draw_length)
      when '+'
        rotate theta
      when '-'
        rotate -theta
      when 'X','Y'                    # do nothing except recognize the 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.64**gen
    @production = grammar.generate gen
  end
end  



 

Saturday, 21 November 2009

Koch Island Using Lindemayer System in Ruby Processing


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

require 'koch'

class Koch < Processing::App
  attr_reader :koch
  def setup
    size 500, 500
    @koch = KochLSystem.new
    koch.simulate 4
  end
 
  def draw
    background 0
    koch.render
    save_frame "/home/tux/koch.png"
  end
end

#####################################################################################
## Koch island using an L-System
#####################################################################################


class KochLSystem
  include Processing::Proxy
  @@steps = 0
  attr_accessor :axiom, :rule, :start_length, :theta, :production, :generations, :draw_length
  def initialize
    @axiom = "F-F-F-F"
    @rule = "F-FF--F-F"
    @start_length = 60.0
    @theta = (Math::PI/180) * 90.0 # convert degrees to radians
    reset
  end

  def reset # initialize or reset variables
    @production = axiom
    @draw_length = start_length
    @generations = 0
  end
 
  def iterate(prod_, rule_)
    @draw_length *=  0.6
    @generations += 1
    new_production = prod_         
    new_production.gsub!('F', rule_)
  end
 
  def render()
    translate(width/2, height/3)
    @@steps += 4        
    @@steps = production.length if @@steps > production.length
    prod = production.split(//)  ### for ruby 1.9 use each_char
    @@steps.times do |i|
      case prod[i]
      when 'F'
        no_fill
        stroke 255
        line(0, 0, 0, -draw_length)       
        translate(0, -draw_length)
      when 'f'               #unused
        translate(0, -draw_length)     
      when '+'
        rotate(theta)
      when '-'
        rotate(-theta)
      when '['               # unused
        push_matrix()
      when ']'               # unused
        pop_matrix()
      else puts "Unrecognised grammar" 
      end
    end
  end
  def simulate(gen)
    while (generations < gen)
      @production = iterate(@production, @rule)
    end
  end
end





 

Thursday, 19 November 2009

Lindenmayer system in ruby-processing


##
# Lindenmayer System in ruby-processing by Martin Prout
# Loosely based on processing Pentigree L-System by Geraldine Sarmiento
###

require 'pentigree'

class Pentigree_Test < Processing::App 
  load_library :grammar
  attr_reader :pentigree
  def setup
    size 800, 800
    @pentigree = Pentigree.new
    pentigree.create_grammar(5)  
    no_loop
  end
  
  def draw
    background 0
    pentigree.render
  end
end

######################
# pentigree.rb
######################
class Pentigree
  include Processing::Proxy  
 
  attr_reader :axiom, :grammar, :start_length, :theta, :production, :generations, :draw_length
  
  def initialize
    @axiom = "F-F-F-F-F"
    @grammar = Grammar.new(axiom)
    grammar.add_rule("F", "F-F++F+F-F-F")
    @start_length = 40.0
    @theta = (Math::PI/180) * 72.0 # convert degrees to radians
    @production = axiom
    @draw_length = start_length
  end
    
  def render 
    translate(width * 0.8, height * 0.75)
    @production.each_char do |prod|      
      case prod
      when 'F'
        no_fill
        stroke 255
        line(0, 0, 0, draw_length)        
        translate(0, draw_length)
      when '+' 
        rotate(theta)
      when '-'
        rotate(-theta)
      when '['
        push_matrix()
      when ']' 
        pop_matrix()
      else            
        puts "Character '#{element}' is not in grammar" 
      end
    end
  end
  
  def create_grammar(gen)
    @draw_length *=  0.6**gen
    @production = grammar.generate gen
  end
end





Tuesday, 17 November 2009

Tessellated "curvy" triangles in ruby-processing


# alhambra.rb by Martin Prout

class CurvyTriangle < Processing::App

  def setup()  
    size(700, 650)
    @x_values = [100, 300, 500, 700]
    @y_values = [50 * Math.sqrt(3), 150 * Math.sqrt(3), 250 * Math.sqrt(3), 350 * Math.sqrt(3)]
    background 21, 15, 72
    smooth
    render
    save_frame "alahambra.png"
  end

  def draw_hexagon(xpos, ypos, sz, theta)
    begin_shape
      6.times do |i|
      vertex(xpos + sz*Math.cos((Math::PI/3 * i) + theta), ypos + sz*Math.sin((Math::PI/3 * i) +theta));
    end
    end_shape CLOSE
  end

  def draw_triangle(x0, y0, sz, color, disp)
    # Calculate  triangle points
    pts = Array.new(12)
    pts[0] = PVector.new(x0, y0 - sz/Math.sqrt(3))               # A
    pts[1] = PVector.new(x0 - 0.5 * sz, y0 + (Math.sqrt(3)*sz)/6)# B
    pts[2] = PVector.new(x0 + 0.5 * sz, y0 + (Math.sqrt(3)*sz)/6)# C
    pts[3] = get_mid_point(pts[0], pts[1])                       # Ab
    pts[4] = get_mid_point(pts[1], pts[2])                       # Bc
    pts[5] = get_mid_point(pts[0], pts[2])                       # Ca
    pts[6] = get_mid_point(pts[0], pts[3])                       # Aba
    adjust_bezier(pts[6], Math::PI/3, disp*sz)                   # Aba
    pts[7] = get_mid_point(pts[3], pts[1])                       # Abb
    adjust_bezier(pts[7], Math::PI/3, -disp*sz)                  # Abb
    pts[8] = get_mid_point(pts[1], pts[4])
    adjust_bezier(pts[8], Math::PI/2, -disp*sz)
    pts[9] = get_mid_point(pts[4], pts[2])
    adjust_bezier(pts[9], Math::PI/2, disp*sz)
    pts[10] = get_mid_point(pts[2], pts[5])
    adjust_bezier(pts[10], -Math::PI/3, -disp*sz)
    pts[11] = get_mid_point(pts[5], pts[0])
    adjust_bezier(pts[11], -Math::PI/3, disp*sz)
    # render triangle
    fill color
    begin_shape()
      vertex(pts[0].x, pts[0].y)
      bezier_vertex(pts[0].x, pts[0].y, pts[6].x, pts[6].y, pts[3].x, pts[3].y)
      bezier_vertex(pts[3].x, pts[3].y, pts[7].x, pts[7].y, pts[1].x, pts[1].y)
      bezier_vertex(pts[1].x, pts[1].y, pts[8].x, pts[8].y, pts[4].x, pts[4].y)
      bezier_vertex(pts[4].x, pts[4].y, pts[9].x, pts[9].y, pts[2].x, pts[2].y)
      bezier_vertex(pts[2].x, pts[2].y, pts[10].x, pts[10].y, pts[5].x, pts[5].y)
      bezier_vertex(pts[5].x, pts[5].y, pts[11].x, pts[11].y, pts[0].x, pts[0].y)
      end_shape(CLOSE)
      # set color and render small hexagon
      fill(255);
      draw_hexagon(x0 + 4, y0, sz * 0.214, 0);
    end
  
    def adjust_bezier(base, theta, disp)
      base.add(PVector.new(Math.cos(theta)*disp, Math.sin(theta)*disp))
    end
  
    def get_mid_point(a, b)
      mid = PVector.add(a, b)
      mid.div(2)
      return mid
    end
  
    def render
      @x_values.length.times do |column|
    
      @y_values.length.times do |row|
        if (row % 2 == 0) then
          if (column % 3 ==0) then
            draw_triangle(@x_values[column], @y_values[row], 200, color(255, 0, 0), 0.32)
          else
            draw_triangle(@x_values[column], @y_values[row], 200, color(255, 0, 255), 0.32)
          end
        # end
        # offset tiles so that they tesselate
        elsif ((column - 2) % 3 ==0) then
            draw_triangle(@x_values[column] - 100, @y_values[row], 200, color(255, 0, 0), 0.32)
          else
            draw_triangle(@x_values[column] - 100, @y_values[row], 200, color(255, 0, 255), 0.32)
          end
        end
      end
    end
end


Wednesday, 11 November 2009

Dynamic rand with ruby-processing context-free DSL

Regular context free can't do dynamic random, here I experiment with the possibilities of ruby-processing DSL. This example is essentially a modified city.rb that is included as a sample with the application. Note I have added a convenience method rand_range for use in the DSL, partly as an experiment in ways of extending the program.


load_library 'context_free'

def setup_the_spots
  @spots = ContextFree.define do
    
    class << self 
      define_method (:rand_range) do |max, min|  # nice to have a range
      rand * (max - min) + min
      end
    end
    
    rule :region do
      split do
        block :x => -0.25, :y => -0.25, :brightness => 1.0, :saturation => 1.0
        rewind
        block :x => 0.25, :y => -0.25, :brightness => 1.0, :saturation => 1.0
        rewind
        block :x => 0.25, :y => 0.25, :brightness => 1.0, :saturation => 1.0
        rewind
        block :x => -0.25, :y => 0.25, :brightness => 1.0, :saturation => 1.0
      end
    end
    
    rule :block do
      spots :size => 0.85, :hue => rand, :saturation => rand_range(1.0, 0.8), :brightness => rand_range(1.0, 0.8)
    end
    
    rule :block, 5 do
      region :size => 0.5, :rotation => rand_range(8, 4), :hue => rand_range(0.8, 0.5), :saturation => rand_range(1.0, 0.5), :brightness => rand_range(1.0, 0.5)
    end
    
    rule :block, 0.1 do
      # Do nothing
    end
    
    rule :spots do
      circle :hue => rand_range(0.8, 0.5), :saturation => 1.0, :brightness => 1.0, :alpha => 0.4
    end
    
  end
end


def setup
  size 600, 600
  background 255, 255, 0
  smooth
  color_mode HSB, 1.0  
  setup_the_spots
  draw_it
end

def draw
  # Do nothing
end

def draw_it
  @spots.render :region, :color => [0.2, 1.0, 1.0, 1.0],
               :start_x => width/2, :start_y => height/2,
               :size => height/2.5
end

def mouse_clicked
  draw_it
end




 

Tuesday, 10 November 2009

Flower in ruby-processing DSL

# flower.rb ruby-processing
load_library 'context_free'
 

ROT = 180*(Math.sqrt(5) - 1)
 

def setup_the_flower
  @flower = ContextFree.define do
    @rot = 0
    @pet = 0
    rule :start do
      flower :brightness => 1.0
    end
 

    rule :flower do
      @rot += ROT
      @rot = @rot % 360
      split do
        petal :brightness => 1.0, :saturation =>  0.5
      rewind
        flower :rotation => @rot, :size =>  0.99, :hue =>  0.15
      rewind
      end
    end
  
    rule :petal do
      @pet += 15
      split do
        triangle :x => 4.1, :rotation => @pet
      rewind
        petal :size => 0.995, :alpha => 0.4, :brightness => 0.5
      #rewind
      end
    end
  end
end
  
def setup
  size 800, 800
  setup_the_flower
  smooth
  draw_it
  save_frame("flower.png")
end


def draw
  # Do nothing.
end


def draw_it
  background 60, 0, 0
  @flower.render :start, :size => width*0.8, :stop_size => 2,
        :start_x => width/2, :start_y => height/2
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