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

Showing posts with label l-system fractal. Show all posts
Showing posts with label l-system fractal. Show all posts

Wednesday, 5 May 2010

Penrose Tiling Ruby-Processing LSystems


######################################################
# A Lindenmayer System in ruby-processing by Martin Prout
# Loosely based on a processing PenroseTiling sketch
# by Geraldine Sarmiento
# tiling.rb
######################################################
require 'penrose_tiling'

class Penrose < Processing::App
  load_libraries "grammar"

  attr_reader :penrose

  def setup
    size 1000, 1000
    stroke 255, 60
    stroke_weight 2
    smooth
    @penrose = PenroseTiling.new
    penrose.create_grammar 4
    no_loop
  end

  def draw
    background 0
    penrose.render
  end
end

########################################
# penrose_tiling.rb
########################################

class PenroseTiling
  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 = PI/5 # radians or 36 degrees

  def initialize
    @axiom = "[X]2+[X]2+[X]2+[X]2+[X]"        # Note use of abbreviated rule
    @grammar = Grammar.new axiom              # here number equals number of repeats
    @grammar.add_rule "F", ""
    @grammar.add_rule "X", "+YF2-ZF[3-WF2-XF]+"
    @grammar.add_rule "Y", "-WF2+XF[3+YF2+ZF]-"
    @grammar.add_rule "Z", "2-YF4+WF[+ZF4+XF]2-XF"
    @grammar.add_rule "W", "YF2+ZF4-XF[-YF4-WF]2+"
    @start_length = 1000.0
    @theta = 0
    @xpos = width/2
    @ypos = height/2
    @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]    # a simple array for turtle
    stack = []                      # a simple array for stack
    production.scan(/./).each do |element|
      case element
      when 'F'
        turtle = draw_line(turtle, draw_length)
      when '+'
        turtle[ANGLE] += DELTA * repeats
        repeats = 1
      when '-'
        turtle[ANGLE] -= DELTA * repeats
        repeats = 1
      when '['
        stack.push(turtle.dup)  # push a copy current turtle to stack
      when ']'
        turtle = stack.pop        # assign current turtle a instance popped from the stack
      when 'W', 'X', 'Y', 'Z'
      when '2', '3', '4'
        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.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 * cos(turtle[ANGLE])
    new_ypos = turtle[YPOS] - length * sin(turtle[ANGLE])
    line(turtle[XPOS], turtle[YPOS], new_xpos, new_ypos)
    return [new_xpos, new_ypos, turtle[ANGLE]]
  end
end

#############################################################
# library/grammar.rb
# Non-stochastic grammar
# 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



  

Monday, 8 March 2010

Towards Post Production Transformation of LSystem Fractals

I've finally got round to reading "Fractals Everywhere" by Michael F. Barnsley. The math is not overly complicated just a bit unfamiliar. Anyway I'm sort of inspired by some the transforms in the book, to see what I could come up with. I'm particularly attracted by the idea of projecting a 2D fractal into a 3D dimensional space. There are possibly too many different ways of do this (including paper modelling, something I'd not considered before I saw it today over on the processing discourse). My first approach will be to produce a data set for a regular 2D fractal, and thereafter apply some rules to create a deformation of the pattern. To this end I've re-worked my snake-kolam to create a data set (an array of points). The first transformation rules I have created (see ScalingTool class) merely scale and center the fractal. Here is the code:-


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

class Kolam_Test < Processing::App
  load_libraries 'kolam'
  attr_reader :kolam

  def setup
    size 500, 500
    @kolam = Kolam.new
    kolam.create_grammar 3      # create grammar from rules
    kolam.translate             # translate grammar to points
    no_loop
  end

  def draw
    background 0
    kolam.render width, height  # adjust points to fit frame & render
  end
end

############################
# library/kolam/kolam.rb
# Non-stochastic grammar
# 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

############################
# snake kolam using l-systems
############################
BORDER = 10 # global border constant
XPOS = 0    # global point array constants
YPOS = 1

class Kolam
  include Processing::Proxy
  attr_accessor :axiom, :xpos, :ypos, :grammar, :production, :draw_length, :points
  ANGLE = 2
  DELTA = (Math::PI/180) * 90.0 # convert degrees to radians using ruby

  def initialize
    @axiom = "FX+F+FX+F"
    @grammar = Grammar.new(axiom)
    grammar.add_rule("X", "X-F-F+FX+F+FX-F-F+FX")
    @theta = DELTA
    @points = [[0, 0]]              # initialize points array with first point
    @draw_length = 1.0
    @production = axiom
    @xpos = 0
    @ypos = 0
  end

  def translate                       # NB not using processing affine transforms here
    turtle = [xpos, ypos, 0.0]
    production.scan(/./).each do |element|
      case element
      when 'F'
        turtle = store_line(turtle, draw_length)
      when '+'
        turtle[ANGLE] += DELTA  
      when '-'
        turtle[ANGLE] -= DELTA  
      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

  def render(width, height)
    st = ScalingTool.new width, height, points
    data = st.scale_to_fit
    no_fill
    stroke(0, 255, 0)
    stroke_width(2)
    begin_shape
    data.each do |point|
      vertex(point[XPOS], point[YPOS])
    end
    end_shape
  end

  ##############################
  # create grammar from axiom and rules
  # leave scaling & postioning to render
  ##############################

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

  private
  ######################################################
  # calculate and store line using current turtle and length parameters
  # returns a turtle corresponding to the new position
  ######################################################

  def store_line(turtle, length)
    new_xpos = turtle[XPOS] + length * Math.cos(turtle[ANGLE])
    new_ypos = turtle[YPOS] + length * Math.sin(turtle[ANGLE])
    *point = new_xpos, new_ypos                 # collect coordinates
    @points.push(point)
    *turtle = new_xpos, new_ypos, turtle[ANGLE] % (Math::PI * 2) # collect coordinates & angle
  end
end

###################################
# scaling tool, scale and center fractal
###################################

class ScalingTool
  attr_reader :max_height, :max_width, :lowest_y, :lowest_x, :highest_y, :highest_x, :raw_data, :scale
  def initialize max_width, max_height, data = []
    @max_width = max_width - BORDER
    @max_height = max_height - BORDER
    @raw_data = data
    @lowest_x = 0
    @lowest_y = 0
    @highest_x = 0
    @highest_y = 0
    @scale = 1
  end

  def scale_to_fit
    processed = raw_data
    scale = calculate_scale_factor
    processed.each do |item|
      item[XPOS] = scale * (item[XPOS] - lowest_x) + BORDER/2
      item[YPOS] = scale * (item[YPOS] - lowest_y) + BORDER/2
    end
    return processed
  end

  private

  def calculate_x_range
    @raw_data.each do |item|
      @lowest_x = item[XPOS] unless lowest_x < item[XPOS]
      @highest_x = item[XPOS] unless highest_x > item[XPOS]   
    end
    highest_x - lowest_x
  end

  def calculate_y_range
    @raw_data.each do |item|
      @lowest_y = item[YPOS] unless lowest_y < item[YPOS]
      @highest_y = item[YPOS] unless highest_y > item[YPOS]
    end
    highest_y - lowest_y
  end

  #################################################
  # Returns the smallest of width or height factors
  # as side effect stores lowest x and y values
  #################################################

  def calculate_scale_factor
    scale_x = (max_height * 1.0)/calculate_x_range
    scale_y = (max_width * 1.0)/calculate_y_range
    (scale_x < scale_y) ? scale_x : scale_y
  end
end




Thursday, 11 February 2010

A Pentive? Fractal

The pentive fractal is another space filling fractal that I found over on a fractint site again for the grammar generator see my Cesàro fractal.

########################################################
# A Pentive fractal implemented using a
# Lindenmayer System in ruby-processing by Martin Prout
########################################################
require 'pentive'

class Pentive_Test < Processing::App
  attr_reader :pentive,:points, :production

  def setup
    size(600, 400)
    @pentive = Pentive.new(width/95, height*0.9)
    @production = pentive.create_grammar(8)
    @points = pentive.translate_rules(production)
    no_loop()
  end

  def draw()
    background(0)
    stroke(255)
    points.each do |tmp|
      line(*tmp)
    end
  end
end

####################################################
# The Pentive? fractal
####################################################
class Pentive

  attr_reader :draw_length, :xpos, :ypos, :theta, :axiom, :grammar, :delta
  DELTA = Math::PI/180 * 36 # 36 degrees

  def initialize xpos, ypos
    @axiom = "Q"  
    @theta  = -DELTA
    @grammar = Grammar.new(axiom)
    grammar.add_rule("F", "")
    grammar.add_rule("P","1-FR3+FS1-FU")   # abbreviated grammar 1 = two & 3 = four repeats
    grammar.add_rule("Q", "FT1+FR3-FS1+")
    grammar.add_rule("R", "1+FP3-FQ1+FT")
    grammar.add_rule("S", "FU1-FP3+FQ1-")  
    grammar.add_rule("T", "+FU1-FP+")
    grammar.add_rule("U", "-FQ1+FT-")
    @draw_length = 12
    @xpos = xpos
    @ypos = ypos
  end

  def create_grammar(gen)  
    grammar.generate(gen)
  end

  def translate_rules(prod)
    repeats = 1
    points = [] # An empty array to store lines as an array of points
    prod.scan(/./) do |ch|
      case(ch)
      when "F"
        temp = [xpos, ypos, (@xpos += draw_length * Math.cos(theta)), (@ypos += draw_length * Math.sin(theta))]
        points.push(temp)    
      when "+"
        @theta += DELTA * repeats
        repeats = 1    
      when "-"
        @theta -= DELTA * repeats
        repeats = 1
      when '1', '3'
        repeats += Integer(ch)
      when "P", "Q", "R", "S", "T", "U"        
      else
        puts("character '#{ch}' not in grammar")
      end
   end
    return points
  end
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