load_libraries 'PeasyCam'
import 'peasy'
attr_reader :cam
def setup()
size(200,200,P3D)
configure_camera()
end
def configure_camera()
@cam = PeasyCam.new(self, 100)
cam.set_minimum_distance(50)
cam.set_maximum_distance(500)
end
def draw()
rotate_x(-0.5)
rotate_y(-0.5)
background(0)
fill(255, 0, 0)
box(30)
push_matrix()
translate(0, 0, 20)
fill(0, 0, 255)
box(5)
pop_matrix()
end
Experiments with ruby-processing (processing-2.2.1) and JRubyArt for processing-3.0
Sunday, 31 January 2010
Saturday, 30 January 2010
Using the PeasyCam library in ruby-processing
Here is good example of how nice it is to have libraries, the PeasyCam library makes configuring the camera a cinch (it also works pretty smoothly). The lsystem grammar library means you can forget about how the production string is generated. What was the question?
########################################################
# A 3D Hilbert fractal implemented using a
# Lindenmayer System in ruby-processing by Martin Prout
# Best if you've got opengl
########################################################
require 'hilbert'
class Hilbert_Test < Processing::App
full_screen # NB: All distances are relative to screen height
load_libraries 'grammar', 'PeasyCam', 'opengl'
import 'peasy'
import "processing.opengl" if library_loaded? "opengl"
attr_reader :hilbert, :cam
def setup()
library_loaded?(:opengl) ? configure_opengl : render_mode(P3D)
configure_peasycam
@hilbert = Hilbert.new(height)
hilbert.create_grammar(3)
no_stroke()
end
def configure_peasycam
cam = PeasyCam.new(self, height/10)
cam.set_minimum_distance(height/10)
cam.set_maximum_distance(height/2)
end
def configure_opengl
render_mode OPENGL
hint ENABLE_OPENGL_4X_SMOOTH # optional
hint DISABLE_OPENGL_ERROR_REPORT # optional
end
def draw()
background(0)
lights()
hilbert.render()
end
end
############################
# hilbert.rb
###########################
class Hilbert
include Processing::Proxy
attr_reader :grammar, :axiom, :production, :premis, :rule,
:theta, :scale_factor, :distance, :phi, :len
def initialize(len)
@axiom = "X"
@grammar = Grammar.new(axiom)
@production = axiom
@premis = "X"
@rule = "^<XF^<XFX-F^>>XFX&F+>>XFX-F>X->"
@len = len
@distance = len/12 # distance value relative to screen height
@theta = Math::PI/180 * 90
@phi = Math::PI/180 * 90
grammar.add_rule(premis, rule)
no_stroke()
end
def render()
translate(-len/42, len/42, -len/42) # use the "answer?" to center the Hilbert
fill(0, 75, 152)
light_specular(204, 204, 204)
specular(255, 255, 255)
shininess(1.0)
production.scan(/./) do |ch|
case(ch)
when "F"
translate(0, distance/-2, 0)
box(distance/9 , distance, distance/9)
translate(0, distance/-2, 0)
when "+"
rotateX(-theta)
when "-"
rotateX(theta)
when ">"
rotateY(theta)
when "<"
rotateY(-theta)
when "&"
rotateZ(-phi)
when "^"
rotateZ(phi)
when "X"
else
puts("character '#{ch}' not in grammar")
end
end
end
##############################
# create grammar from axiom and
# rules (adjust scale)
##############################
def create_grammar(gen)
@distance *= 0.5**gen
@production = @grammar.generate gen
end
end
########################################################
# A 3D Hilbert fractal implemented using a
# Lindenmayer System in ruby-processing by Martin Prout
# Best if you've got opengl
########################################################
require 'hilbert'
class Hilbert_Test < Processing::App
full_screen # NB: All distances are relative to screen height
load_libraries 'grammar', 'PeasyCam', 'opengl'
import 'peasy'
import "processing.opengl" if library_loaded? "opengl"
attr_reader :hilbert, :cam
def setup()
library_loaded?(:opengl) ? configure_opengl : render_mode(P3D)
configure_peasycam
@hilbert = Hilbert.new(height)
hilbert.create_grammar(3)
no_stroke()
end
def configure_peasycam
cam = PeasyCam.new(self, height/10)
cam.set_minimum_distance(height/10)
cam.set_maximum_distance(height/2)
end
def configure_opengl
render_mode OPENGL
hint ENABLE_OPENGL_4X_SMOOTH # optional
hint DISABLE_OPENGL_ERROR_REPORT # optional
end
def draw()
background(0)
lights()
hilbert.render()
end
end
############################
# hilbert.rb
###########################
class Hilbert
include Processing::Proxy
attr_reader :grammar, :axiom, :production, :premis, :rule,
:theta, :scale_factor, :distance, :phi, :len
def initialize(len)
@axiom = "X"
@grammar = Grammar.new(axiom)
@production = axiom
@premis = "X"
@rule = "^<XF^<XFX-F^>>XFX&F+>>XFX-F>X->"
@len = len
@distance = len/12 # distance value relative to screen height
@theta = Math::PI/180 * 90
@phi = Math::PI/180 * 90
grammar.add_rule(premis, rule)
no_stroke()
end
def render()
translate(-len/42, len/42, -len/42) # use the "answer?" to center the Hilbert
fill(0, 75, 152)
light_specular(204, 204, 204)
specular(255, 255, 255)
shininess(1.0)
production.scan(/./) do |ch|
case(ch)
when "F"
translate(0, distance/-2, 0)
box(distance/9 , distance, distance/9)
translate(0, distance/-2, 0)
when "+"
rotateX(-theta)
when "-"
rotateX(theta)
when ">"
rotateY(theta)
when "<"
rotateY(-theta)
when "&"
rotateZ(-phi)
when "^"
rotateZ(phi)
when "X"
else
puts("character '#{ch}' not in grammar")
end
end
end
##############################
# create grammar from axiom and
# rules (adjust scale)
##############################
def create_grammar(gen)
@distance *= 0.5**gen
@production = @grammar.generate gen
end
end
Labels:
PeasyCam library,
ruby processing
Friday, 29 January 2010
3D Hilbert Using LSystems and ruby-processing
Here is a 3D Hilbert in ruby-processing, you can see the basic shape if you set the number of repeats to 1. For me the best look was 3 iterations (starts to look a bit fuzzy with 4 repeats).
I used this application to test my grammar (and lsystem vanilla processing) library on 32 bit Windows (XP). Which worked OK for me, because linux has a bit of an issue about OPENGL with ruby-processing unless fullscreen (vanilla processing works s fine) I have used P3D here.
I'm sure it could work a bit better using opengl. It would be cool to use my vanilla library with ruby-processing but I can't find an interface for the java char in ruby....
########################################################
# A 3D Hilbert fractal implemented using a
# Lindenmayer System in ruby-processing by Martin Prout
########################################################
require 'hilbert'
class Hilbert_Test < Processing::App
load_libraries :grammar, :control_panel
attr_reader :hilbert, :y, :x
attr_accessor :x_view, :y_view, :zoom, :repeat
def setup()
size(500, 500, P3D)
setup_panel
@x_view = 400.0;
@y_view = 0.0;
@zoom = 9.0;
@hilbert = Hilbert.new
hilbert.create_grammar(3)
no_stroke()
camera(x_view, y_view, zoom, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)
translate(width / 2, height / 2)
end
def setup_panel
control_panel do |c|
c.title = "Control:"
c.menu(:repeat, ['1', '2', '3', '4'], '3') {|m| load_menu_item(m) }
c.slider(:zoom, -100..400, zoom)
c.slider(:x_view, 300..700, x_view)
c.slider(:y_view, -400..400, y_view)
c.button :reset!
end
end
def reset!
x_view = 0.0
y_view = 0.0
zoom = 5
@hilbert = Hilbert.new
hilbert.create_grammar(repeat.to_i)
end
def load_menu_item(item)
repeat = item
end
def draw()
background(0)
hilbert.render()
ambient_light(0, 255, 0)
directional_light(0, 255, 0, 0.0, -1.0, 0.0)
camera(x_view, y_view, zoom, # eyeX, eyeY, eyeZ
0.0, 0.0, 0.0, # centerX, centerY, centerZ
0.0, 1.0, 0.0)
end
end
############################
# hilbert.rb
###########################
class Hilbert
include Processing::Proxy
attr_reader :grammar, :axiom, :production, :premis, :rule,
:theta, :scale_factor, :distance, :phi
def initialize()
@axiom = "X"
@grammar = Grammar.new(axiom)
@production = axiom
@premis = "X"
@rule = "^<XF^<XFX-F^>>XFX&F+>>XFX-F>X->"
@distance = 160
@theta = Math::PI/180 * 90
@phi = Math::PI/180 * 90
grammar.add_rule(premis, rule)
no_stroke()
end
def render()
production.scan(/./) do |ch|
case(ch)
when "F"
fill(0, 200, 0)
translate(0, distance/-2, 0)
box(distance/9 , distance, distance/9)
translate(0, distance/-2, 0)
when "+"
rotateX(-theta)
when "-"
rotateX(theta)
when ">"
rotateY(theta)
when "<"
rotateY(-theta)
when "&"
rotateZ(-phi)
when "^"
rotateZ(phi)
when "X"
else
puts("character '#{ch}' not in grammar")
end
end
end
##############################
# create grammar from axiom and
# rules (adjust scale)
##############################
def create_grammar(gen)
@distance *= 0.5**gen
@production = @grammar.generate gen
end
end
I used this application to test my grammar (and lsystem vanilla processing) library on 32 bit Windows (XP). Which worked OK for me, because linux has a bit of an issue about OPENGL with ruby-processing unless fullscreen (vanilla processing works s fine) I have used P3D here.
I'm sure it could work a bit better using opengl. It would be cool to use my vanilla library with ruby-processing but I can't find an interface for the java char in ruby....
########################################################
# A 3D Hilbert fractal implemented using a
# Lindenmayer System in ruby-processing by Martin Prout
########################################################
require 'hilbert'
class Hilbert_Test < Processing::App
load_libraries :grammar, :control_panel
attr_reader :hilbert, :y, :x
attr_accessor :x_view, :y_view, :zoom, :repeat
def setup()
size(500, 500, P3D)
setup_panel
@x_view = 400.0;
@y_view = 0.0;
@zoom = 9.0;
@hilbert = Hilbert.new
hilbert.create_grammar(3)
no_stroke()
camera(x_view, y_view, zoom, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)
translate(width / 2, height / 2)
end
def setup_panel
control_panel do |c|
c.title = "Control:"
c.menu(:repeat, ['1', '2', '3', '4'], '3') {|m| load_menu_item(m) }
c.slider(:zoom, -100..400, zoom)
c.slider(:x_view, 300..700, x_view)
c.slider(:y_view, -400..400, y_view)
c.button :reset!
end
end
def reset!
x_view = 0.0
y_view = 0.0
zoom = 5
@hilbert = Hilbert.new
hilbert.create_grammar(repeat.to_i)
end
def load_menu_item(item)
repeat = item
end
def draw()
background(0)
hilbert.render()
ambient_light(0, 255, 0)
directional_light(0, 255, 0, 0.0, -1.0, 0.0)
camera(x_view, y_view, zoom, # eyeX, eyeY, eyeZ
0.0, 0.0, 0.0, # centerX, centerY, centerZ
0.0, 1.0, 0.0)
end
end
############################
# hilbert.rb
###########################
class Hilbert
include Processing::Proxy
attr_reader :grammar, :axiom, :production, :premis, :rule,
:theta, :scale_factor, :distance, :phi
def initialize()
@axiom = "X"
@grammar = Grammar.new(axiom)
@production = axiom
@premis = "X"
@rule = "^<XF^<XFX-F^>>XFX&F+>>XFX-F>X->"
@distance = 160
@theta = Math::PI/180 * 90
@phi = Math::PI/180 * 90
grammar.add_rule(premis, rule)
no_stroke()
end
def render()
production.scan(/./) do |ch|
case(ch)
when "F"
fill(0, 200, 0)
translate(0, distance/-2, 0)
box(distance/9 , distance, distance/9)
translate(0, distance/-2, 0)
when "+"
rotateX(-theta)
when "-"
rotateX(theta)
when ">"
rotateY(theta)
when "<"
rotateY(-theta)
when "&"
rotateZ(-phi)
when "^"
rotateZ(phi)
when "X"
else
puts("character '#{ch}' not in grammar")
end
end
end
##############################
# create grammar from axiom and
# rules (adjust scale)
##############################
def create_grammar(gen)
@distance *= 0.5**gen
@production = @grammar.generate gen
end
end
Labels:
3D Hilbert,
control panel,
ruby processing
Thursday, 28 January 2010
Two dimensional Hilbert fractal with LSystems and ruby-processing
#############################################################
# A Hilbert fractal implemented (uses my grammar library)
# Lindenmayer System in ruby-processing by Martin Prout
#############################################################
require 'hilbert'
class Hilbert_Test < Processing::App
load_library :grammar
attr_reader :hilbert
def setup
size 600, 600
@hilbert = Hilbert.new
hilbert.create_grammar 5
no_loop
end
def draw
background 0
hilbert.render
end
end
############################
# hilbert.rb
###########################
class Hilbert
include Processing::Proxy
attr_accessor :grammar, :axiom, :draw_length, :turtle
DELTA = Math::PI/2
XPOS = 0 # placeholders for turtle
YPOS = 1
THETA = 2
def initialize
@axiom = "FL"
@grammar = Grammar.new axiom
grammar.add_rule "L", "+RF-LFL-FR+"
grammar.add_rule "R", "-LF+RFR+FL-"
@draw_length = 200
stroke 0, 255, 0
stroke_weight 2
@turtle = Array.new(3) # using an array as turtle
turtle[XPOS] = width/9
turtle[YPOS] = height/9
turtle[THETA] = 0
end
def render
axiom.scan(/./) do |element|
case element
when 'F' # NB NOT using affine transforms
draw_line(turtle)
when '+'
@turtle[THETA] += DELTA
when '-'
@turtle[THETA] -= DELTA
when 'L'
when 'R'
else puts "Grammar not recognized"
end
end
end
def draw_line(turtle)
x_temp = turtle[XPOS]
y_temp = turtle[YPOS]
@turtle[XPOS] += draw_length * Math.cos(turtle[THETA])
@turtle[YPOS] += draw_length * Math.sin(turtle[THETA])
line(x_temp, y_temp, turtle[XPOS], turtle[YPOS])
end
##############################
# create grammar from axiom and
# rules (adjust scale)
##############################
def create_grammar(gen)
@draw_length *= 0.6**gen
@production = @grammar.generate gen
end
end
Labels:
Hilbert fractal,
LSystems,
ruby processing
Monday, 25 January 2010
Fern Fractal Using LSystems (includes a subtle color change)
Here is another LSystem example using the control panel to set the number of 'generations' of the fractal. This sketch requires my grammar library see the Cesà ro fractal.
##############################################
# fern_test.rb inspired by processing fern by Gareth Spor
##############################################
require 'fern'
class FernTest < Processing::App
load_libraries :control_panel, :grammar
attr_accessor :init_color, :fern, :repeat
def setup()
size(800, 800)
setup_panel
@init_color = color(0, 240, 0)
redraw
end
def draw()
background(0)
fern.render(250.0, 650.0, 0.0, 100.0, init_color)
end
def setup_panel
control_panel do |c|
c.title = "Control:"
c.menu(:repeat, ['10', '12', '16', '20'], '12') {|m| load_menu_item(m) }
c.button(:redraw)
end
end
def redraw
@fern = Fern.new()
@production = fern.create_grammar(repeat.to_i)
end
def load_menu_item(item)
repeat = item
end
end
######################
# fern.rb
######################
class Fern
include Processing::Proxy
XPOS = 0 # place holders for pen array
YPOS = 1
THETA = 2
LENGTH = 3
HUE = 4
DELTA = Math::PI/180 * 35.2 # NB: 36 is boring
attr_accessor :pen, :production, :grammar, :axiom
def initialize
@axiom = "FD"
@grammar = Grammar.new(axiom)
grammar.add_rule("D", "C+@FD")
grammar.add_rule("C", "B")
grammar.add_rule("B", "[7+#FD][7-#FD]") # abbreviated lsystem grammar
@production = axiom
@pen = Array.new(5) # use a simple array for pen (for efficiency)
end
def render(x, y, theta, len, col)
pen[XPOS] = x
pen[YPOS] = y
pen[THETA] = -Math::PI/2
pen[LENGTH] = len
pen[HUE] = col
repeats = 1
stack = Array.new # use a locally defined array as the pen stack
@production.each_char do |element|
case(element)
when "F" # move forward
@pen = draw_line(pen)
when "+" #turn right
pen[THETA] += (2 * Math::PI) % (repeats * DELTA)
repeats = 1
when '-' #turn left
pen[THETA] -= (2 * Math::PI) % (repeats * DELTA)
repeats = 1
when "#" #resize line length & darken color
pen[LENGTH] *= 0.33
pen[HUE] = decrement_color(20)
when "@" #resize line length & darken color
pen[LENGTH] *= 0.9
pen[HUE] = decrement_color(10)
when "[" #push state
stack.push(@pen.dup)
when "]" #pop state
@pen = stack.pop()
when "7"
repeats = Integer(element)
when "B" # do nothing except confirm character in grammar
when "C" # do nothing except confirm character in grammar
when "D" # do nothing except confirm character in grammar
else
puts("character '#{element}' not in grammar")
end
end
end
def draw_line(pen)
temp = pen.clone
temp[XPOS] += temp[LENGTH] * Math.cos(temp[THETA])
temp[YPOS] += temp[LENGTH] * Math.sin(temp[THETA])
stroke_weight(2)
stroke(temp[HUE])
line(pen[XPOS], pen[YPOS], temp[XPOS], temp[YPOS])
return temp
end
def decrement_color(hue_step)
gree = green(pen[HUE]) - hue_step
color(0, gree, 0)
end
def create_grammar(gen)
@production = grammar.generate(gen)
end
end
Labels:
fern fractal,
l-systems,
ruby processing
Monday, 18 January 2010
Using a symbolic grammar for LSystems ruby-processing
Here I map characters of the LSystem grammar to human readable symbols. This I do by creating a hash. I then convert axiom strings to an array of symbols. The grammar is then "produced" as an array of symbols. The renderer the interprets the production as an array of symbols (it might use less memory than non-symbolic version but is it any faster???). I don't seem to be able use collect, so I need to create a new array of symbols when generating the grammar.
##
# A Sierpinski Triangle implemented using a
# Lindenmayer System in ruby-processing by Martin Prout
###
require 'symbol'
class Sierpinski_Test < Processing::App
load_library :grammar
attr_reader :sierpinski
def setup
size 610, 600
@sierpinski = Sierpinski.new
sierpinski.create_grammar(8)
no_loop
end
def draw
background 0
sierpinski.render
end
end
############################
# sierpinski.rb
###########################
class Sierpinski
include Processing::Proxy
attr_accessor :axiom, :grammar, :start_length, :production, :draw_length, :xpos, :ypos, :symbol_map
XPOS = 0
YPOS = 1
ANGLE = 2
DELTA = (Math::PI/180) * 120 # convert degrees to radians
def initialize
@start_length = 300
setup_grammar
@xpos = width/60
@ypos = height*0.9
stroke 255
@draw_length = start_length
end
def setup_grammar
@axiom = "FX"
@symbol_map = {"X" => :X, "+" => :PLUS, "-" => :MINUS, "F" => :FORWARD}
@grammar = Grammar.new(axiom, symbol_map)
grammar.add_rule "F", "FF" # replace F rule see grammar library
grammar.add_rule "X", "-FXF+FXF+FXF-" # replace X rule see grammar library
@production = axiom
end
def render # NB not using affine transforms here
turtle = [xpos, ypos, 0.0]
production.each do |element|
case element
when :FORWARD
turtle = draw_line(turtle, draw_length)
when :PLUS
turtle[ANGLE] += DELTA # rotate by + theta if going the affine transform route
when :MINUS
turtle[ANGLE] -= DELTA # rotate by - theta if going the affine transform route
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
##############################
# 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 * 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 symbolic grammar
# with unique premise/rules
############################
class Grammar
attr_accessor :axiom, :rules, :symbol_map
def initialize(axiom, symbol_map)
@symbol_map = symbol_map
@axiom = string_to_symbol_array(axiom)
@rules = Hash.new
end
def string_to_symbol_array(a_string)
sym_array = []
a_string.scan(/./).each do |ch|
sym_array.push(symbol_map[ch])
end
return sym_array
end
def add_rule premise, rule
skey = symbol_map[premise]
svalue = string_to_symbol_array(rule)
rules.store(skey, svalue)
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
##########################################
# replace each pre symbol with un-splatted symbol array
# from rules lookup
##########################################
def new_production production
prod = []
production.each do |c|
(r = rules[c]) ? prod.push(*r) : prod.push(c)
end
return prod
end
end
##
# A Sierpinski Triangle implemented using a
# Lindenmayer System in ruby-processing by Martin Prout
###
require 'symbol'
class Sierpinski_Test < Processing::App
load_library :grammar
attr_reader :sierpinski
def setup
size 610, 600
@sierpinski = Sierpinski.new
sierpinski.create_grammar(8)
no_loop
end
def draw
background 0
sierpinski.render
end
end
############################
# sierpinski.rb
###########################
class Sierpinski
include Processing::Proxy
attr_accessor :axiom, :grammar, :start_length, :production, :draw_length, :xpos, :ypos, :symbol_map
XPOS = 0
YPOS = 1
ANGLE = 2
DELTA = (Math::PI/180) * 120 # convert degrees to radians
def initialize
@start_length = 300
setup_grammar
@xpos = width/60
@ypos = height*0.9
stroke 255
@draw_length = start_length
end
def setup_grammar
@axiom = "FX"
@symbol_map = {"X" => :X, "+" => :PLUS, "-" => :MINUS, "F" => :FORWARD}
@grammar = Grammar.new(axiom, symbol_map)
grammar.add_rule "F", "FF" # replace F rule see grammar library
grammar.add_rule "X", "-FXF+FXF+FXF-" # replace X rule see grammar library
@production = axiom
end
def render # NB not using affine transforms here
turtle = [xpos, ypos, 0.0]
production.each do |element|
case element
when :FORWARD
turtle = draw_line(turtle, draw_length)
when :PLUS
turtle[ANGLE] += DELTA # rotate by + theta if going the affine transform route
when :MINUS
turtle[ANGLE] -= DELTA # rotate by - theta if going the affine transform route
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
##############################
# 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 * 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 symbolic grammar
# with unique premise/rules
############################
class Grammar
attr_accessor :axiom, :rules, :symbol_map
def initialize(axiom, symbol_map)
@symbol_map = symbol_map
@axiom = string_to_symbol_array(axiom)
@rules = Hash.new
end
def string_to_symbol_array(a_string)
sym_array = []
a_string.scan(/./).each do |ch|
sym_array.push(symbol_map[ch])
end
return sym_array
end
def add_rule premise, rule
skey = symbol_map[premise]
svalue = string_to_symbol_array(rule)
rules.store(skey, svalue)
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
##########################################
# replace each pre symbol with un-splatted symbol array
# from rules lookup
##########################################
def new_production production
prod = []
production.each do |c|
(r = rules[c]) ? prod.push(*r) : prod.push(c)
end
return prod
end
end
Labels:
l-systems,
ruby processing
Saturday, 16 January 2010
Islands in ruby processing using my grammar library
See Cesàro fractal for grammar.rb library
require 'island'
class Test_Island < Processing::App
load_libraries "grammar"
attr_reader :island
def setup
size 500, 500
stroke 255
smooth
@island = Island.new
island.create_grammar 2
no_loop
end
def draw
background 0
island.render
end
end
######################################################
# island.rb
######################################################
class Island
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) * 90.0 # convert degrees to radians
def initialize
@axiom = "F-F-F-F"
@grammar = Grammar.new axiom
grammar.add_rule("F", "F+f-FF+F+FF+Ff+FF-f+FF-F-FF-Ff-FFF")
grammar.add_rule("f", "ffffff")
@start_length = 1200.0
@theta = 0
@xpos = width/4
@ypos = height*0.8
@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()
turtle = [xpos, ypos, theta] # simple array for turtle
production.scan(/./).each do |element|
case element
when 'F'
turtle = draw_line(turtle, draw_length)
when 'f'
turtle = forward(turtle, draw_length/2)
when '+'
turtle[ANGLE] += DELTA
when '-'
turtle[ANGLE] -= DELTA
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.08**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 * 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
def forward(turtle, length)
new_xpos = turtle[XPOS] + length * Math.cos(turtle[ANGLE])
new_ypos = turtle[YPOS] + length * Math.sin(turtle[ANGLE])
turtle = [new_xpos, new_ypos, turtle[ANGLE]]
end
end
require 'island'
class Test_Island < Processing::App
load_libraries "grammar"
attr_reader :island
def setup
size 500, 500
stroke 255
smooth
@island = Island.new
island.create_grammar 2
no_loop
end
def draw
background 0
island.render
end
end
######################################################
# island.rb
######################################################
class Island
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) * 90.0 # convert degrees to radians
def initialize
@axiom = "F-F-F-F"
@grammar = Grammar.new axiom
grammar.add_rule("F", "F+f-FF+F+FF+Ff+FF-f+FF-F-FF-Ff-FFF")
grammar.add_rule("f", "ffffff")
@start_length = 1200.0
@theta = 0
@xpos = width/4
@ypos = height*0.8
@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()
turtle = [xpos, ypos, theta] # simple array for turtle
production.scan(/./).each do |element|
case element
when 'F'
turtle = draw_line(turtle, draw_length)
when 'f'
turtle = forward(turtle, draw_length/2)
when '+'
turtle[ANGLE] += DELTA
when '-'
turtle[ANGLE] -= DELTA
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.08**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 * 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
def forward(turtle, length)
new_xpos = turtle[XPOS] + length * Math.cos(turtle[ANGLE])
new_ypos = turtle[YPOS] + length * Math.sin(turtle[ANGLE])
turtle = [new_xpos, new_ypos, turtle[ANGLE]]
end
end
Labels:
Islands fractal,
ruby processing
Thursday, 14 January 2010
Refactoring my Penrose Snowflake
Dan Mayer over at devver.net has kindly analysed and re-factored my penrose_snowflake with caliper (something I've never heard of but then I'm not really in the community, just ploughing my own furrow). He also extended my code in an interesting way to produce a "snowflake screensaver" which labours a bit.
Well I tried out Dans refactoring on my own code (which I had already changed to have a separate grammar library). Here is my revised penrose_snowflake.rb (requires grammar.rb and runs with penrose.rb) incorporating some additional refactoring, which you may or may not like depending on your view of the ternary operator (my multiplier has fewer lines of code and is 4% faster according to my benchmark test). However I have since come up with a different version which I have now used to replace my original posting!!! I think the new version will come into its own for my bracketed L-Systems and my Island fractal which is next on the list for refactoring.
class PenroseSnowflake
include Processing::Proxy
attr_accessor :axiom, :grammar, :start_length, :theta, :production, :draw_length,
:repeats, :xpos, :ypos
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 or L-Systems in my iterate
# 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
production.each_char do |element|
case element
when 'F'
line(xpos, ypos, (@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 += 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.4**gen
@production = grammar.generate gen
end
def multiplier(repeats, type)
value = draw_length * repeats
(type == :cos)? value * Math.cos(theta) : value * Math.sin(theta)
end
end
Well I tried out Dans refactoring on my own code (which I had already changed to have a separate grammar library). Here is my revised penrose_snowflake.rb (requires grammar.rb and runs with penrose.rb) incorporating some additional refactoring, which you may or may not like depending on your view of the ternary operator (my multiplier has fewer lines of code and is 4% faster according to my benchmark test). However I have since come up with a different version which I have now used to replace my original posting!!! I think the new version will come into its own for my bracketed L-Systems and my Island fractal which is next on the list for refactoring.
class PenroseSnowflake
include Processing::Proxy
attr_accessor :axiom, :grammar, :start_length, :theta, :production, :draw_length,
:repeats, :xpos, :ypos
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 or L-Systems in my iterate
# 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
production.each_char do |element|
case element
when 'F'
line(xpos, ypos, (@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 += 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.4**gen
@production = grammar.generate gen
end
def multiplier(repeats, type)
value = draw_length * repeats
(type == :cos)? value * Math.cos(theta) : value * Math.sin(theta)
end
end
Labels:
caliper,
re-factor,
ruby processing,
ternary operator
Subscribe to:
Posts (Atom)
Followers
Blog Archive
-
▼
2010
(73)
-
▼
January
(14)
- Hello Peasy in ruby processing (a simple PeasyCam ...
- Using the PeasyCam library in ruby-processing
- 3D Hilbert Using LSystems and ruby-processing
- Two dimensional Hilbert fractal with LSystems and ...
- Fern Fractal Using LSystems (includes a subtle col...
- Using a symbolic grammar for LSystems ruby-processing
- Islands in ruby processing using my grammar library
- Refactoring my Penrose Snowflake
- A 3D Plant using L-Systems and ruby-processing
- Another control panel example with odd turtle turns
- Adding some controls to Sierpinski sketch
- A Sierpinski Variant using L-Systems and ruby proc...
- An interactive l-system sketch using the Control P...
- Checking the probabilities
-
▼
January
(14)
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