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

Showing posts with label penrose snowflake. Show all posts
Showing posts with label penrose snowflake. Show all posts

Monday, 21 October 2013

Penrose Snowflake Revisited

The 1st time I revisited this sketch was in response to some refactoring by Dan Myer see http://learning-ruby-processing.blogspot.co.uk/2010/01/refactoring-my-penrose-snowflake.html. Since the advent of OPENGL for two dimensional sketches, that is all old hat, since we no longer need to keep generating the geometry, we can create a shape and reuse it (multiple times in the same sketch if need be). So here we use a generic grammar library for simple lsystems:-
############################
# Simple lsystem grammar
############################
class Grammar

  attr_reader :axiom, :rules
  def initialize(axiom, rules)
    @axiom = axiom
    @rules = rules
  end

  def expand(production, iterations, &block)
    production.each_char do |token|
      if rules.has_key?(token) && iterations > 0
        expand(rules[token], iterations - 1, &block)
      else
        yield token
      end
    end
  end

  def each gen
    expand(axiom, gen) {|token| yield token }
  end

  def generate gen
    output = []
    each(gen){ |token| output << token }
    return output
  end

end


Here is the animation code adapted from a processing example:-
# A class to describe a Polygon (with a PShape)

class Polygon
  include Processing::Proxy
  # The PShape object
  attr_reader :s, :x, :y, :speed, :height

  def initialize(s_, width, height)
    @x = rand(width)
    @y = rand(height * -1.5 .. -height / 3)
    @s = s_
    @speed = rand(2 .. 6)
    @height = height
  end

  # Simple motion
  def move
    @y +=speed
    @y = -100 if (y > height + 100)
  end

  # Draw the object
  def display
    push_matrix
    translate(x, y)
    shape(s)
    pop_matrix
  end

  def run
    display
    move
  end
end


Here is the penrose snowflake
load_libraries :grammar, :polygon

class PenroseSnowflake
  include Processing::Proxy
  import 'grammar'

  attr_accessor :axiom, :grammar, :start_length, :theta, :production, :draw_length,
  :repeats, :xpos, :ypos
  DELTA = Math::PI / 10 # 18 degrees as radians

  def initialize width
    reset width
  end

  def reset width
    @axiom = "F3-F3-F3-F3-F"
    @grammar = Grammar.new( axiom,
      {"F" => "F3-F3-F45-F++F3-F"}
      )
    @start_length = width / 40
    @theta = 0
    @xpos = width / 80
    @ypos = 0
    @draw_length = start_length
  end

  ##############################################################################
  # Parses the production string, and draws a line when 'F' is found, uses
  # trignometry to calculate dx and dy rather than processing transforms
  ##############################################################################

  def create_flake production
    flake = create_shape
    fil = [200, 220, 255].sample
    flake.begin_shape
    flake.fill fil
    flake.stroke fil
    flake.vertex(xpos, ypos)
    repeats = 1
    production.each do |element|
      case element
      when 'F'
        flake.vertex(@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 += element.to_i
      else
        puts "Character '#{element}' is not in grammar"
      end
    end
    flake.end_shape CLOSE
    return flake
  end

  ##########################################
  # adjust draw length with number of repeats
  # uses grammar to set production string 
  # see 'grammar.rb'
  ##########################################

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

  ###########################################
  # a helper method that returns dx or dy with type & repeat
  # multiplier after Dan Mayer
  ###########################################

  def multiplier(repeats, type)
    value = draw_length * repeats
    # using equal? for identity comparison
    (type.equal? :cos)?  value * cos(theta) : value *  sin(theta)
  end
end

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

attr_reader :polygons

def setup
  size displayWidth, displayHeight, P2D
  #stroke 255
  flakes = []
  @polygons = []
  penrose = PenroseSnowflake.new width
  (2 .. 4).each do |i|
    production = penrose.create_grammar(i)
    flakes << penrose.create_flake(production)
    penrose.reset width
  end
  40.times do
    polygons << Polygon.new(flakes.sample, width, height)
  end
end

def draw
  background 0
  # Display and move them all
  polygons.each do |poly|
    poly.run
  end
end



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



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