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
Experiments with ruby-processing (processing-2.2.1) and JRubyArt for processing-3.0
Sunday, 22 November 2009
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
##
# 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
Labels:
heighway dragon,
Lindenmayer system,
ruby processing
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
Labels:
Lindenmayer system,
ruby processing
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
Labels:
l-system,
Lindenmayer system,
ruby-processing
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
Labels:
ruby processing,
tessellation
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

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

Labels:
context free,
DSL,
random,
ruby processing
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
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
Labels:
DSL,
flower,
ruby-processing
Subscribe to:
Posts (Atom)
Followers
About Me
- monkstone
- I have developed JRubyArt and propane new versions of ruby-processing for JRuby-9.1.5.0 and processing-3.2.2





