############################ # 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
No comments:
Post a Comment