Peano curve using my custom library
######################################################
# peano_test.rb
#
# Lindenmayer System in ruby-processing by Martin Prout
######################################################
require 'grammar'
require 'peano'
class Peano_Test < Processing::App
attr_reader :peano
def setup
size 1000, 1000
@peano = Peano.new
peano.create_grammar 4
no_loop
end
def draw
background 0
peano.render
end
end
############################
# peano.rb
#
# Peano Fractal
###########################
class Peano
include Processing::Proxy
require 'grammar'
DELTA = (Math::PI/180) * 60.0 # convert degrees to radians
attr_accessor :axiom, :grammar, :start_length, :theta, :production, :xpos, :ypos, :draw_length
def initialize
@axiom = "XF"
@grammar = Grammar.new axiom
grammar.add_rule 'X', "X+YF++YF-FX--FXFX-YF+" ## replace X with this string see grammar library
grammar.add_rule 'Y', "-FX+YFYF++YF+FX--FX-Y" ## replace Y with this string see grammar library
@start_length = 120.0
@theta = 0.0
@xpos = width/3
@ypos = height/10
@production = axiom
@draw_length = start_length
end
def render()
@production.each_char do |element|
case element
when 'F'
line(@xpos, @ypos, (@xpos -= (draw_length * Math.cos(theta))), (@ypos += (draw_length * Math.sin(theta))))
when '+'
@theta += DELTA
when '-'
@theta -= DELTA
when 'X','Y' ## do nothing except recognize the grammar
else puts "Grammar not recognized"
end
end
end
def create_grammar(gen)
stroke 255
@draw_length *= 0.6**gen
@production = grammar.generate gen
end
end
Experiments with ruby-processing (processing-2.2.1) and JRubyArt for processing-3.0
Monday, 28 December 2009
Peano curve fractal using Lindenmayer Systems
Labels:
l-system,
Peano curve
Saturday, 19 December 2009
3D Lindenmayer Systems using the anar+ library with ruby-processing
When you start this sketch one block is drawn, by repeatedly pressing the space-bar you create a new iteration, and more blocks are drawn (don't go mad or at least ensure there is enough memory allocated/available) scrolling zooms you in and out and you eventually you can kind-off step through the plane off the screen. Hold down the middle mouse button to rotate the plane of the image (prepare to be amazed, at what a little script can do).
load_libraries 'anar', 'opengl'
import "anar" if library_loaded? "anar"
import "lsys.Grammar" if library_loaded? "anar"
import "processing.opengl" if library_loaded? "opengl"
full_screen
attr_accessor :my_obj, :grammar, :box, :ts, :tf, :init_t, :r, :s
def setup()
library_loaded?(:opengl) ? setup_opengl : fail_opengl
hint(ENABLE_OPENGL_4X_SMOOTH)
Anar.init(self)
Anar.draw_axis(true)
init_grammar()
interpret_init()
end
def init_grammar()
@grammar = Grammar.new("bt")
# here define the rules
# * means any kind of symbol
# the example rules below are therefore non contextual
grammar.add_rule("*b*", "brb")
grammar.add_rule("*t*", "rssb")
grammar.add_rule("*r*", "rs")
grammar.add_rule("*s*", "s")
# this one makes it context dependant
# grammar.add_rule("sss", "ss")
puts(grammar)
end
def interpret_init
# base element
@box = Box.new(10, 10, 50)
# initial position
@init_t = Translate.new(0, 50, 0)
# base transformations
@ts = Translate.new(Anar.Pt(0, 10, -0.001))
@r = RotateZ.new(0.5)
@s = Scale.new(Anar.Pt(0.99, 0.99, 0.99))
interpret_grammar()
end
def interpret_grammar()
@my_obj = Obj.new
@tf = Transform.new(init_t)
grammar.num_of_symbols.times do |i|
case(grammar.symbol(i)[0].chr) # todo this is bit of hack, I need understand grammar better
when 'b'
my_copy = Obj.new(box, tf)
my_obj.add(my_copy)
@tf = Transform.new(tf)
when 't'
tf.apply(ts)
when 'r'
tf.apply(r)
when 's'
tf.apply(s)
else
puts "grammar not understood"
end
end
Anar.cam_target(my_obj)
end
def draw()
background(153)
my_obj.draw()
end
def key_pressed()
if(key==' ')
grammar.step()
interpret_grammar()
puts(grammar)
end
if(key=='r')
grammar.reset()
interpret_init()
interpret_grammar()
puts(grammar)
end
if(key=='p')
save_frame("block.png")
end
end
def setup_opengl
render_mode OPENGL
hint ENABLE_OPENGL_4X_SMOOTH #optional
end
def fail_opengl
abort "!!!You absolutely need opengl for this sketch!!!"
end
load_libraries 'anar', 'opengl'
import "anar" if library_loaded? "anar"
import "lsys.Grammar" if library_loaded? "anar"
import "processing.opengl" if library_loaded? "opengl"
full_screen
attr_accessor :my_obj, :grammar, :box, :ts, :tf, :init_t, :r, :s
def setup()
library_loaded?(:opengl) ? setup_opengl : fail_opengl
hint(ENABLE_OPENGL_4X_SMOOTH)
Anar.init(self)
Anar.draw_axis(true)
init_grammar()
interpret_init()
end
def init_grammar()
@grammar = Grammar.new("bt")
# here define the rules
# * means any kind of symbol
# the example rules below are therefore non contextual
grammar.add_rule("*b*", "brb")
grammar.add_rule("*t*", "rssb")
grammar.add_rule("*r*", "rs")
grammar.add_rule("*s*", "s")
# this one makes it context dependant
# grammar.add_rule("sss", "ss")
puts(grammar)
end
def interpret_init
# base element
@box = Box.new(10, 10, 50)
# initial position
@init_t = Translate.new(0, 50, 0)
# base transformations
@ts = Translate.new(Anar.Pt(0, 10, -0.001))
@r = RotateZ.new(0.5)
@s = Scale.new(Anar.Pt(0.99, 0.99, 0.99))
interpret_grammar()
end
def interpret_grammar()
@my_obj = Obj.new
@tf = Transform.new(init_t)
grammar.num_of_symbols.times do |i|
case(grammar.symbol(i)[0].chr) # todo this is bit of hack, I need understand grammar better
when 'b'
my_copy = Obj.new(box, tf)
my_obj.add(my_copy)
@tf = Transform.new(tf)
when 't'
tf.apply(ts)
when 'r'
tf.apply(r)
when 's'
tf.apply(s)
else
puts "grammar not understood"
end
end
Anar.cam_target(my_obj)
end
def draw()
background(153)
my_obj.draw()
end
def key_pressed()
if(key==' ')
grammar.step()
interpret_grammar()
puts(grammar)
end
if(key=='r')
grammar.reset()
interpret_init()
interpret_grammar()
puts(grammar)
end
if(key=='p')
save_frame("block.png")
end
end
def setup_opengl
render_mode OPENGL
hint ENABLE_OPENGL_4X_SMOOTH #optional
end
def fail_opengl
abort "!!!You absolutely need opengl for this sketch!!!"
end
Labels:
anar+ library,
l-system,
ruby processing
Friday, 18 December 2009
Another Example of Using Anar+ lib in ruby-processing
NB you absolutely must try rotating the plane of the image (hold central mouse button or equivalent down), and zoom into the shape/building area...
load_libraries 'anar', 'opengl'
import "anar" if library_loaded? "anar"
import "processing.opengl" if library_loaded? "opengl"
attr_accessor :my_obj, :group
full_screen
def setup()
library_loaded?(:opengl) ? setup_opengl : fail_opengl
Anar.init(self)
Anar.draw_axis()
Face.globalRender = RenderFaceNormal.new(OogColor.new(255, 100), OogColor.new(100))
init_form()
end
def init_form()
@my_obj = Obj.new
@group = Group.new
###################
#CONE
###################
cone = Cone.new(50,100,20)
cone.set("cone")
cone.translate(100,100,0)
puts(cone.to_obj_exporter())
group.add(cone)
###################
#BOX
###################
box = Box.new(10,20,30)
box.set("box")
box.rotate_z(0)
box.rotate_x(0)
box.translate(100, 0, 0)
puts(box.to_obj_exporter("box"))
puts(box.parent_list(-1))
group.add(box)
###################
#CYLINDER
###################
cylinder = Cylinder.new(50, 24, 50)
cylinder.set("cylinder")
cylinder.translate(-100, 0, 0)
puts(cylinder.to_obj_exporter("cylinder"))
group.add(cylinder)
###################
#ELLIPSE
###################
ellipse = Ellipse.new(40, 20)
ellipse.set("ellipse")
puts(ellipse.to_obj_exporter("ellipse"))
group.add(ellipse)
###################
#SWISSCROSS3D
###################
swissCross3D = SwissCross3D.new(10, 10)
swissCross3D.set("swissCross3D")
#swissCross3D.fill(255,0,0,200)
puts(swissCross3D.to_obj_exporter("swissCross3D"))
#swissCross3D.translate(-100,0,0)
group.add(swissCross3D)
###################
#REVOLVER
###################
ctrlRevol = Pts.new()
ctrlRevol.add(Anar.Pt(30,0,30))
ctrlRevol.add(Anar.Pt(10,0,40))
ctrlRevol.add(Anar.Pt(20,0,60))
ctrlRevol.add(Anar.Pt(20,0,70))
revolver = Revolve.new(ctrlRevol, Anar.Pt(0,0,20), 12)
revolver.set("revolver")
puts(revolver.to_obj_exporter("revolver"))
group.add(revolver)
##################
###################
my_obj.add(box)
my_obj.add(cone)
my_obj.add(cylinder)
my_obj.add(ellipse)
my_obj.add(swissCross3D)
my_obj.add(revolver)
Anar.sliders(swissCross3D)
Anar.sliders(revolver)
Anar.cam_target(revolver)
end
def fail_opengl
abort "!!!You absolutely need opengl for this sketch!!!"
end
def setup_opengl
render_mode OPENGL
hint ENABLE_OPENGL_4X_SMOOTH #optional
end
def draw()
background(155)
group.draw()
end
def key_pressed()
if(key==' ') then
init_form
end
end
load_libraries 'anar', 'opengl'
import "anar" if library_loaded? "anar"
import "processing.opengl" if library_loaded? "opengl"
attr_accessor :my_obj, :group
full_screen
def setup()
library_loaded?(:opengl) ? setup_opengl : fail_opengl
Anar.init(self)
Anar.draw_axis()
Face.globalRender = RenderFaceNormal.new(OogColor.new(255, 100), OogColor.new(100))
init_form()
end
def init_form()
@my_obj = Obj.new
@group = Group.new
###################
#CONE
###################
cone = Cone.new(50,100,20)
cone.set("cone")
cone.translate(100,100,0)
puts(cone.to_obj_exporter())
group.add(cone)
###################
#BOX
###################
box = Box.new(10,20,30)
box.set("box")
box.rotate_z(0)
box.rotate_x(0)
box.translate(100, 0, 0)
puts(box.to_obj_exporter("box"))
puts(box.parent_list(-1))
group.add(box)
###################
#CYLINDER
###################
cylinder = Cylinder.new(50, 24, 50)
cylinder.set("cylinder")
cylinder.translate(-100, 0, 0)
puts(cylinder.to_obj_exporter("cylinder"))
group.add(cylinder)
###################
#ELLIPSE
###################
ellipse = Ellipse.new(40, 20)
ellipse.set("ellipse")
puts(ellipse.to_obj_exporter("ellipse"))
group.add(ellipse)
###################
#SWISSCROSS3D
###################
swissCross3D = SwissCross3D.new(10, 10)
swissCross3D.set("swissCross3D")
#swissCross3D.fill(255,0,0,200)
puts(swissCross3D.to_obj_exporter("swissCross3D"))
#swissCross3D.translate(-100,0,0)
group.add(swissCross3D)
###################
#REVOLVER
###################
ctrlRevol = Pts.new()
ctrlRevol.add(Anar.Pt(30,0,30))
ctrlRevol.add(Anar.Pt(10,0,40))
ctrlRevol.add(Anar.Pt(20,0,60))
ctrlRevol.add(Anar.Pt(20,0,70))
revolver = Revolve.new(ctrlRevol, Anar.Pt(0,0,20), 12)
revolver.set("revolver")
puts(revolver.to_obj_exporter("revolver"))
group.add(revolver)
##################
###################
my_obj.add(box)
my_obj.add(cone)
my_obj.add(cylinder)
my_obj.add(ellipse)
my_obj.add(swissCross3D)
my_obj.add(revolver)
Anar.sliders(swissCross3D)
Anar.sliders(revolver)
Anar.cam_target(revolver)
end
def fail_opengl
abort "!!!You absolutely need opengl for this sketch!!!"
end
def setup_opengl
render_mode OPENGL
hint ENABLE_OPENGL_4X_SMOOTH #optional
end
def draw()
background(155)
group.draw()
end
def key_pressed()
if(key==' ') then
init_form
end
end
Labels:
anar+ library,
CAD,
ruby-processing
Thursday, 17 December 2009
Using the Anar+ library in ruby-processing
First you need to put both opengl and anar libraries in eponymous folders (same name) and them both in an outer folder library, then your sketch can load the libraries 'easy-peasy'. If you are not on linux, you do not need not run full screen but you should because its great. This sketch apart from the full_screen is virtually a straight translation of a java processing example from http://anar.ch. NB Use scroll wheel to zoom or equivalent gizmo.
load_libraries 'anar', 'opengl'
import "anar" if library_loaded? "anar"
import "processing.opengl" if library_loaded? "opengl"
attr_accessor :my_obj
full_screen
def setup()
library_loaded?(:opengl) ? setup_opengl : fail_opengl
frame_rate(200)
Anar.init(self)
Anar.draw_axis(true)
init_form()
end
def setup_opengl
render_mode OPENGL
hint ENABLE_OPENGL_4X_SMOOTH #optional
end
def init_form()
#Create a new Line
pts = Pts.new
pts.add(Anar.Pt(100,110,20))
pts.add(Anar.Pt(110,100,40))
pts.add(Anar.Pt(110,90,60))
pts.add(Anar.Pt(90,90,90))
#Create a Face
f = Star.new(50,100,5)
#Extrude the face along the Line
@my_obj = Extrude.new(f,pts)
Anar.cam_target(my_obj)
Anar.sliders(my_obj)
end
def draw()
background(155)
my_obj.draw()
end
def fail_opengl
abort "!!!You absolutely need opengl for this sketch!!!"
end
#Toggle the display of the sliders
def keyPressed()
Anar.sliders_toggle()
end
Original author Guillaume LaBelle (I think?), sliders are bit tiny and fall off the end but functionality is superb (there might have been sound as there is some complaint about lack of promidi, however nothing happened for me when I loaded the appropriate libraries).
load_libraries 'anar', 'opengl'
import "anar" if library_loaded? "anar"
import "processing.opengl" if library_loaded? "opengl"
attr_accessor :my_obj
full_screen
def setup()
library_loaded?(:opengl) ? setup_opengl : fail_opengl
frame_rate(200)
Anar.init(self)
Anar.draw_axis(true)
init_form()
end
def setup_opengl
render_mode OPENGL
hint ENABLE_OPENGL_4X_SMOOTH #optional
end
def init_form()
#Create a new Line
pts = Pts.new
pts.add(Anar.Pt(100,110,20))
pts.add(Anar.Pt(110,100,40))
pts.add(Anar.Pt(110,90,60))
pts.add(Anar.Pt(90,90,90))
#Create a Face
f = Star.new(50,100,5)
#Extrude the face along the Line
@my_obj = Extrude.new(f,pts)
Anar.cam_target(my_obj)
Anar.sliders(my_obj)
end
def draw()
background(155)
my_obj.draw()
end
def fail_opengl
abort "!!!You absolutely need opengl for this sketch!!!"
end
#Toggle the display of the sliders
def keyPressed()
Anar.sliders_toggle()
end
Original author Guillaume LaBelle (I think?), sliders are bit tiny and fall off the end but functionality is superb (there might have been sound as there is some complaint about lack of promidi, however nothing happened for me when I loaded the appropriate libraries).
Labels:
3D stuff,
anar+ library,
opengl,
ruby processing
Thursday, 10 December 2009
Stochastic Plant rules Lindenmayer Systems in ruby processing
#######################################################
# stochastic_test.rb
#
# Lindenmayer System in ruby-processing by Martin Prout
# Exploring terminals with minimum logic
########################################################
require 'stochastic_plant'
class Stochastic_Test < Processing::App
attr_reader :stochastic, :stochastic1, :stochastic2
def setup
size 1000, 800
@stochastic = StochasticPlant.new 200, 700
@stochastic1 = StochasticPlant.new 500, 700
@stochastic2 = StochasticPlant.new 700, 700
stochastic.create_grammar 5
stochastic1.create_grammar 4 # simpler plant
stochastic2.create_grammar 5
no_loop
end
def draw
background 0
stroke(0, 255, 0)
stroke_width(3)
stochastic.render
stochastic1.render
stochastic2.render
end
end
############################
# stochastic_plant.rb
# includes option for no pen
# move forward, unused here
###########################
require 'stochastic_grammar'
class StochasticPlant
include Processing::Proxy
attr_reader :grammar, :axiom, :draw_length, :theta, :xpos, :ypos, :production, :pen_down
XPOS = 0 # placeholders for turtle array
YPOS = 1
ANGLE = 2
DELTA = PI/8
def initialize xpos_, ypos_
@draw_length = 350
@xpos = xpos_
@ypos = ypos_
@theta = PI/2 # this way is up?
setup_grammar
end
def setup_grammar
@axiom = "F"
@grammar = StochasticGrammar.new(axiom)
grammar.add_rule("F", "F[+F]F[-F]F", 0.1)
grammar.add_rule("F", "F[+F]F", 0.45)
grammar.add_rule("F", "F[-F]F", 0.45)
@production = axiom
@pen_down = false
end
def render
stack = Array.new # ruby array as the turtle stack
turtle = [xpos, ypos, theta] # ruby array as a turtle
production.scan(/./).each do |element|
case element
when 'F' # NB NOT using affine transforms
@pen_down = true
turtle = draw_line(turtle, draw_length)
when '+'
turtle[ANGLE] += DELTA
when '-'
turtle[ANGLE] -= DELTA
when '['
stack.push(turtle.dup) # push a copy of the current turtle to stack
when ']'
turtle = stack.pop # assign current turtle to an instance popped from the stack
else
puts "Character '#{element}' is not in grammar"
end
end
end
def create_grammar gen
@draw_length *= 0.5**gen
@production = grammar.generate gen
end
private
######################################################
# draws a line using current turtle and length parameters
# unless pen_down = false (then only move forward)
# returns a turtle corresponding to the new position
######################################################
def draw_line(turtle, length)
new_xpos = turtle[XPOS] + length * Math.cos(turtle[ANGLE])
new_ypos = turtle[YPOS] - length * Math.sin(turtle[ANGLE])
line(turtle[XPOS], turtle[YPOS], new_xpos, new_ypos) if pen_down
@pen_down = false
turtle = [new_xpos, new_ypos, turtle[ANGLE]]
end
end
########################
# stochastic_grammar.rb
# unweighted rules accepted
# with default weight = 1
# complex stochastic rule
########################
class StochasticGrammar
PROB = 1
attr_accessor :axiom, :srules
def initialize axiom
@axiom = axiom
@srules = Hash.new # rules dictionary (a hash of hashes)
end
######################################################
# randomly selects a rule (with a weighted probability)
#####################################################
def stochastic_rule(rules)
total = rules.inject(0) do |total, rule_and_weight|
total += rule_and_weight[PROB]
end
srand
chance = rand * total
rules.each do |rule, weight|
return item unless chance > weight
chance -= weight
end
return rule
end
def has_rule?(pre)
@srules.has_key?(pre)
end
def add_rule(pre, rule, weight = 1) # default weighting 1 (can handle non-stochastic rules)
if (has_rule?(pre)) then # add to existing hash
srules[pre].store rule, weight
else
temp = Hash.new # create a new hash for rule/weights
temp.store rule, weight # add to new hash
srules.store pre, temp # store new hash with pre key
end
end
def new_production(prod)
prod.gsub!(/./) do |ch|
(has_rule?(ch)) ? stochastic_rule(srules[ch]) : ch
end
end
def generate(repeat = 0)
prod = axiom
repeat.times do
prod = new_production(prod)
end
return prod
end
end
Labels:
ruby-processing,
stochastic l-system rules
Tuesday, 8 December 2009
A square edge fractal with L-System and ruby-processing
LSystem using my custom grammar library (see Cesàro fractal in earlier post)
########################################################
# Chequer implemented using a grammar library
# Lindenmayer System in ruby-processing by Martin Prout
########################################################
require 'chequer'
class Chequer_Test < Processing::App
load_libraries 'grammar'
attr_reader :chequer
def setup
size 600, 600
@chequer = Chequer.new
chequer.create_grammar 4
no_loop
end
def draw
background 0
chequer.render
end
end
############################
# chequer.rb
###########################
class Chequer
include Processing::Proxy
attr_accessor :axiom, :grammar, :production, :draw_length, :theta, :xpos, :ypos
DELTA = Math::PI/2
def initialize
@axiom = "F-F-F-F"
@grammar = Grammar.new axiom
@grammar.add_rule "F", "FF-F-F-F-FF"
@draw_length = 500
stroke 0, 255, 0
stroke_weight 2
@xpos = width * 0.9
@ypos = height/10
@theta = 0
end
def render
stack = Array.new # directly using locally defined array as turtle stack
production.each_char do |element|
case element
when 'F' # NB NOT using affine transforms
x_temp = xpos
y_temp = ypos
@xpos -= draw_length * Math.cos(theta) # looks neater here
@ypos -= draw_length * Math.sin(theta)
line(x_temp, y_temp, xpos, ypos)
when '+'
@theta += DELTA
when '-'
@theta -= DELTA
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 /= 3**gen
@production = @grammar.generate gen
end
end
########################################################
# Chequer implemented using a grammar library
# Lindenmayer System in ruby-processing by Martin Prout
########################################################
require 'chequer'
class Chequer_Test < Processing::App
load_libraries 'grammar'
attr_reader :chequer
def setup
size 600, 600
@chequer = Chequer.new
chequer.create_grammar 4
no_loop
end
def draw
background 0
chequer.render
end
end
############################
# chequer.rb
###########################
class Chequer
include Processing::Proxy
attr_accessor :axiom, :grammar, :production, :draw_length, :theta, :xpos, :ypos
DELTA = Math::PI/2
def initialize
@axiom = "F-F-F-F"
@grammar = Grammar.new axiom
@grammar.add_rule "F", "FF-F-F-F-FF"
@draw_length = 500
stroke 0, 255, 0
stroke_weight 2
@xpos = width * 0.9
@ypos = height/10
@theta = 0
end
def render
stack = Array.new # directly using locally defined array as turtle stack
production.each_char do |element|
case element
when 'F' # NB NOT using affine transforms
x_temp = xpos
y_temp = ypos
@xpos -= draw_length * Math.cos(theta) # looks neater here
@ypos -= draw_length * Math.sin(theta)
line(x_temp, y_temp, xpos, ypos)
when '+'
@theta += DELTA
when '-'
@theta -= DELTA
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 /= 3**gen
@production = @grammar.generate gen
end
end
Labels:
l-system,
square fractal
Monday, 7 December 2009
Seasonal sketch, adding terminals to L-System plant
Revised version as of 10th January 2010 now uses my grammar library.
########################################################
# Lindenmayer System in ruby-processing by Martin Prout
# Exploring terminals with minimum logic (Seasonal)
########################################################
require 'seasonal'
class Seasonal_Test < Processing::App
load_library :grammar
attr_reader :seasonal
def setup
size 600, 800
@seasonal = Seasonal.new
seasonal.create_grammar(3)
no_loop
end
def draw
background 0
seasonal.render
end
end
############################
# seasonal.rb
###########################
class Seasonal
include Processing::Proxy
attr_reader :grammar, :axiom, :draw_length, :theta, :xpos, :ypos, :production
DELTA = Math::PI/4
def initialize
@axiom = "F"
@grammar = Grammar.new axiom
grammar.add_rule("F", "F[-F]F[+F][F]")
@draw_length = 600
@xpos = width/2
@ypos = height * 0.9
@theta = Math::PI/2 # this way is up?
end
def render
stack = Array.new # directly using locally defined array as turtle stack
production.each_char do |element|
case element
when 'F' # NB NOT using affine transforms
x_temp = xpos
y_temp = ypos
@xpos += draw_length * Math.cos(theta) * 0.5 # looks neater here
@ypos -= draw_length * Math.sin(theta) * 0.5
stroke 0, 255, 0
stroke_weight 4
line(x_temp, y_temp, xpos, ypos)
when '+'
@theta += DELTA
when '-'
@theta -= DELTA
when '['
stack.push([xpos, ypos, theta])
when ']'
no_stroke
fill 255, 0, 0
# add some seasonal red berries as terminals using minimal logic
ellipse(xpos, ypos, draw_length/3, draw_length/3) unless stack.length < 2
@xpos, @ypos, @theta = *stack.pop
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.5**gen
@production = @grammar.generate gen
end
end
########################################################
# Lindenmayer System in ruby-processing by Martin Prout
# Exploring terminals with minimum logic (Seasonal)
########################################################
require 'seasonal'
class Seasonal_Test < Processing::App
load_library :grammar
attr_reader :seasonal
def setup
size 600, 800
@seasonal = Seasonal.new
seasonal.create_grammar(3)
no_loop
end
def draw
background 0
seasonal.render
end
end
############################
# seasonal.rb
###########################
class Seasonal
include Processing::Proxy
attr_reader :grammar, :axiom, :draw_length, :theta, :xpos, :ypos, :production
DELTA = Math::PI/4
def initialize
@axiom = "F"
@grammar = Grammar.new axiom
grammar.add_rule("F", "F[-F]F[+F][F]")
@draw_length = 600
@xpos = width/2
@ypos = height * 0.9
@theta = Math::PI/2 # this way is up?
end
def render
stack = Array.new # directly using locally defined array as turtle stack
production.each_char do |element|
case element
when 'F' # NB NOT using affine transforms
x_temp = xpos
y_temp = ypos
@xpos += draw_length * Math.cos(theta) * 0.5 # looks neater here
@ypos -= draw_length * Math.sin(theta) * 0.5
stroke 0, 255, 0
stroke_weight 4
line(x_temp, y_temp, xpos, ypos)
when '+'
@theta += DELTA
when '-'
@theta -= DELTA
when '['
stack.push([xpos, ypos, theta])
when ']'
no_stroke
fill 255, 0, 0
# add some seasonal red berries as terminals using minimal logic
ellipse(xpos, ypos, draw_length/3, draw_length/3) unless stack.length < 2
@xpos, @ypos, @theta = *stack.pop
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.5**gen
@production = @grammar.generate gen
end
end
Labels:
l-system,
ruby-processing,
terminals
A Cesàro (or torn square) fractal in ruby processing (using a grammar library)
Includes code for grammar library...
To use the library as a local library put grammar.rb in a folder grammar, and put that folder
in a library folder along with the ruby code (otherwise place it with the other libraries in the distribution, in my test local libraries get called first).
#############################################################
# A Cesaro fractal implemented (uses a grammar.rb library)
# Lindenmayer System in ruby-processing by Martin Prout
#############################################################
require 'cesaro'
class Cesaro_Test < Processing::App
load_library :grammar
attr_reader :cesaro
def setup
size 600, 600
@cesaro = Cesaro.new
cesaro.create_grammar 5
no_loop
end
def draw
background 0
cesaro.render
end
end
############################
# cesaro.rb
###########################
class Cesaro
include Processing::Proxy
attr_reader :grammar, :axiom, :draw_length, :theta, :xpos, :ypos, :production
XPOS = 0 # placeholders for turtle array
YPOS = 1
ANGLE = 2
DELTA = Math::PI/16
def initialize
setup_grammar
@draw_length = 200
stroke 0, 255, 0
stroke_weight 2
@xpos = width/7
@ypos = height/7
@theta = Math::PI/2 # this way is up?
end
def setup_grammar
@axiom = "F7-F7-F7-F"
@grammar = Grammar.new axiom
grammar.add_rule "F", "F6-F67+F6-F"
@production = axiom
end
def render
repeats = 1
stack = Array.new # directly using a locally defined array as the turtle stack
turtle = [xpos, ypos, theta] # simple array for turtle
production.scan(/./).each do |element|
case element
when 'F' # NB NOT using affine transforms
turtle = draw_line(turtle, draw_length, repeats)
repeats = 1
when '+'
turtle[ANGLE] += DELTA * repeats
repeats = 1
when '-'
turtle[ANGLE] -= DELTA * repeats
repeats = 1
when '6', '7' # NB '6' means seven repeats etc.
repeats += Integer(element)
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.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, repeats)
length *= repeats
new_xpos = turtle[XPOS] + length * Math.cos(turtle[ANGLE])
new_ypos = turtle[YPOS] + length * Math.sin(turtle[ANGLE])
line(turtle[XPOS], turtle[YPOS], new_xpos, new_ypos)
turtle = [new_xpos, new_ypos, turtle[ANGLE]]
end
end
############################
# grammar.rb Non-stochastic grammar
# library 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
To use the library as a local library put grammar.rb in a folder grammar, and put that folder
in a library folder along with the ruby code (otherwise place it with the other libraries in the distribution, in my test local libraries get called first).
#############################################################
# A Cesaro fractal implemented (uses a grammar.rb library)
# Lindenmayer System in ruby-processing by Martin Prout
#############################################################
require 'cesaro'
class Cesaro_Test < Processing::App
load_library :grammar
attr_reader :cesaro
def setup
size 600, 600
@cesaro = Cesaro.new
cesaro.create_grammar 5
no_loop
end
def draw
background 0
cesaro.render
end
end
############################
# cesaro.rb
###########################
class Cesaro
include Processing::Proxy
attr_reader :grammar, :axiom, :draw_length, :theta, :xpos, :ypos, :production
XPOS = 0 # placeholders for turtle array
YPOS = 1
ANGLE = 2
DELTA = Math::PI/16
def initialize
setup_grammar
@draw_length = 200
stroke 0, 255, 0
stroke_weight 2
@xpos = width/7
@ypos = height/7
@theta = Math::PI/2 # this way is up?
end
def setup_grammar
@axiom = "F7-F7-F7-F"
@grammar = Grammar.new axiom
grammar.add_rule "F", "F6-F67+F6-F"
@production = axiom
end
def render
repeats = 1
stack = Array.new # directly using a locally defined array as the turtle stack
turtle = [xpos, ypos, theta] # simple array for turtle
production.scan(/./).each do |element|
case element
when 'F' # NB NOT using affine transforms
turtle = draw_line(turtle, draw_length, repeats)
repeats = 1
when '+'
turtle[ANGLE] += DELTA * repeats
repeats = 1
when '-'
turtle[ANGLE] -= DELTA * repeats
repeats = 1
when '6', '7' # NB '6' means seven repeats etc.
repeats += Integer(element)
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.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, repeats)
length *= repeats
new_xpos = turtle[XPOS] + length * Math.cos(turtle[ANGLE])
new_ypos = turtle[YPOS] + length * Math.sin(turtle[ANGLE])
line(turtle[XPOS], turtle[YPOS], new_xpos, new_ypos)
turtle = [new_xpos, new_ypos, turtle[ANGLE]]
end
end
############################
# grammar.rb Non-stochastic grammar
# library 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
Labels:
l-system,
ruby processing,
torn square fractal
Subscribe to:
Posts (Atom)
Followers
Blog Archive
-
▼
2009
(50)
-
▼
December
(8)
- Peano curve fractal using Lindenmayer Systems
- 3D Lindenmayer Systems using the anar+ library wit...
- Another Example of Using Anar+ lib in ruby-processing
- Using the Anar+ library in ruby-processing
- Stochastic Plant rules Lindenmayer Systems in ruby...
- A square edge fractal with L-System and ruby-proce...
- Seasonal sketch, adding terminals to L-System plant
- A Cesàro (or torn square) fractal in ruby processi...
-
▼
December
(8)
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