LSystem rules from a paper by Tong Lin at UMBC Maryland. The following code could be simplified if you could rely on string index being a char (ruby 1.9) rather than ascii value (ruby 1.8), tripped me up at first, ruby-processing is still at 1.8 (it depends on JRuby which will start supporting ruby 1.9 from JRuby version 1.6?)
Update 10 March 2011 just tried jruby-complete-1.6-RC3 still get old behaviour ie ascii value rather than char when indexing a string, nor is ord method supported still requires Jeremys shim to string.
########################################################
# cs_test.rb
# A 3D Plant implemented using a Context Sensitive
# Lindenmayer System in ruby-processing
# by Martin Prout (3 March 2011)
########################################################
class CS_Test < Processing::App
full_screen # NB: All distances are relative to screen height
load_libraries 'csplant', 'PeasyCam', 'opengl'
import 'peasy'
import "processing.opengl" if library_loaded? "opengl"
attr_reader :csplant, :cam
def setup
library_loaded?(:opengl) ? configure_opengl : render_mode(P3D)
configure_peasycam
@csplant = CSPlant.new(height)
csplant.create_grammar 5
no_stroke
end
def configure_peasycam
cam = PeasyCam.new self, height / 6.5
cam.set_minimum_distance height / 10
cam.set_maximum_distance height
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
csplant.render
end
end
The PeasyCam and csplant libraries need to be nested in the usual way in a library folder. This code is much more complicated than my previous post. This is mainly due to the need to ignore certain symbols when determining context, and hence need to navigate along the production string.
######################################
# csplant.rb
# A library used to implement
# a context sensitive
# 1-L lsystem grammar in
# ruby-processing
# by Martin Prout (4 March 2011)
######################################
################################################
# A helper class stores cs prefix idx, and cchar
# uses idx and char to determine context and
# pre to access context sensitive rule from rules
###############################################
class CSRule
attr_accessor :pre
def initialize pre
@pre = pre
end
def idx
x = 0
if (pre[1] == 60) # NB comparing ascii values until ruby 1.9 support
x -= 1
end
if (pre[1] == 62) # ">"[0] = 62, "<"[0] = 60
x += 1
end
return x
end
def cchar
return pre[0]
end
end
##################################
# The grammar class stores lsystem rules
# in rules Hash, and context Hash if applicable
# In production context is checked (and applied)
# using get_rule method
##################################
class CSGrammar
IGNORE = "[]+-^&3" # characters to ignore for context as a constant string
attr_reader :axiom, :context, :rules, :count
def initialize(axiom)
@axiom = axiom
@rules = Hash.new
@context = Hash.new
end
def add_rule(pre, rule)
if pre.length == 3
context.store(pre[2], CSRule.new(pre))
end
rules.store pre, rule
end
def generate(repeat = 0) # repeat iteration grammar rules
prod = axiom
repeat.times do
prod = new_production(prod)
end
return prod
end
def new_production prod # single iteration grammar rules
@count = 0 # initialise count on axiom or new prod
prod.gsub!(/./) do |ch|
get_rule(prod, ch)
end
end
def get_rule prod, ch
rule = ch # default is to return original character as rule (ie no change)
idx = count # idx is a local index, used to navigate the production string
if (context.has_key?(ch[0]))
while IGNORE.include?(prod[context[ch[0]].idx + idx].chr)
idx += context[ch[0]].idx
end
if prod[context[ch[0]].idx + idx] == context[ch[0]].cchar
rule = rules[context[ch[0]].pre]
else
rule = rules[ch] if rules.has_key?(ch) # context free rule if it exists
end
else
rule = rules[ch] if rules.has_key?(ch) # context free rule if it exists
end
@count += 1 # increment the index of axiom/production as a side effect
return rule
end
end
############
# CSPlant
############
class CSPlant
include Processing::Proxy
attr_reader :grammar, :axiom, :production, :premis, :rule,
:theta, :scale_factor, :distance, :phi, :len
def initialize(len)
@axiom = "F"
@grammar = CSGrammar.new(axiom)
@production = axiom
@len = len
@distance = len/4 # distance value relative to screen height
@theta = Math::PI/180 * 25
@phi = Math::PI/180 * 25
grammar.add_rule("F", "F[-EF[3&A]]E[+F[3^A]]")
grammar.add_rule("F<E", "F[&F[3+A]][^F[3-A]]") # context sensitive rule
no_stroke()
end
def render()
fill(0, 75, 152)
light_specular(204, 204, 204)
specular(255, 255, 255)
shininess(1.0)
repeat = 1
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 * repeat)
repeat = 1
when "-"
rotateX(theta * repeat)
repeat = 1
when "&"
rotateZ(-phi * repeat)
repeat = 1
when "^"
rotateZ(phi * repeat)
repeat = 1
when "3"
repeat = 3
when "["
push_matrix
when "]"
pop_matrix
when "E", "A"
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
Experiments with ruby-processing (processing-2.2.1) and JRubyArt for processing-3.0
Thursday, 3 March 2011
Tuesday, 1 March 2011
Exploring Context Sensitive LSystem rules in ruby / ruby-processing
Warning the following code depends on ruby 1.9, where string index returns a char rather than an ascii value (ruby 1.8) see following post for how this tripped me up at first when exploring a proper ruby-processing example. (Update 5 March 2011)
######################################
# cs_grammar.rb a context sensitive
# 1-L lsystem grammar for
# ruby/ruby-processing
# by Martin Prout (1 March 2011)
######################################
#######################
# A helper class stores
# cs rules idx, and cchar
# methods extract cs data
#######################
class CSRule
attr_accessor :pre, :crule
def initialize pre, crule
@pre, @crule = pre, crule
end
def idx
x = 0
if (pre[1] == "<")
x -= 2
end
return x
end
def cchar
return pre[0]
end
end
##################################
# The grammar class stores rules
# in two Hashes, one for cs rules,
# one for context free rules. Rules
# are filtered on input, and context
# is checked using get_rule in production
##################################
class CSGrammar
attr_reader :axiom, :context, :no_context, :idx
def initialize(axiom)
@axiom = axiom
@no_context = Hash.new
@context = Hash.new
end
def add_rule(pre, rule)
case pre.length
when 3
@context.store(pre[2], CSRule.new(pre, rule)) # index, context, rule
when 1
@no_context.store pre, rule
else print "unrecognized grammar '#{pre}'"
end
end
def generate(repeat = 0) # repeat iteration grammar rules
prod = axiom
repeat.times do
prod = new_production(prod)
end
return prod
end
def new_production prod # single iteration grammar rules
@idx = 0
prod.gsub!(/./) do |ch|
get_rule(prod, ch)
end
end
def get_rule prod, ch
rule = ch # default is return original character as rule (no change)
@idx += 1 # increment the index of axiom/production as a side effect
if (context.has_key?(ch)) && (prod[context[ch].idx + idx] == context[ch].cchar)
rule = context[ch].crule # use context sensitive rule
else
rule = no_context[ch] if no_context.has_key?(ch) # context free rule if it exists
end
return rule
end
end
# Test data taken from ABOP
7.times do |i|
grammar = CSGrammar.new("baaaaaa")
grammar.add_rule("b<a", "b") # context sensitive rule replace a when preceded by b
grammar.add_rule("b", "a")
result = grammar.generate(i)
print result << "\n"
end
Note the way b 'travels' from left to right through the production string in the test output.
Reference:
The Algorithmic Beauty of Plants
Przemyslaw Prusinkiewicz
Aristid Lindenmayer
######################################
# cs_grammar.rb a context sensitive
# 1-L lsystem grammar for
# ruby/ruby-processing
# by Martin Prout (1 March 2011)
######################################
#######################
# A helper class stores
# cs rules idx, and cchar
# methods extract cs data
#######################
class CSRule
attr_accessor :pre, :crule
def initialize pre, crule
@pre, @crule = pre, crule
end
def idx
x = 0
if (pre[1] == "<")
x -= 2
end
return x
end
def cchar
return pre[0]
end
end
##################################
# The grammar class stores rules
# in two Hashes, one for cs rules,
# one for context free rules. Rules
# are filtered on input, and context
# is checked using get_rule in production
##################################
class CSGrammar
attr_reader :axiom, :context, :no_context, :idx
def initialize(axiom)
@axiom = axiom
@no_context = Hash.new
@context = Hash.new
end
def add_rule(pre, rule)
case pre.length
when 3
@context.store(pre[2], CSRule.new(pre, rule)) # index, context, rule
when 1
@no_context.store pre, rule
else print "unrecognized grammar '#{pre}'"
end
end
def generate(repeat = 0) # repeat iteration grammar rules
prod = axiom
repeat.times do
prod = new_production(prod)
end
return prod
end
def new_production prod # single iteration grammar rules
@idx = 0
prod.gsub!(/./) do |ch|
get_rule(prod, ch)
end
end
def get_rule prod, ch
rule = ch # default is return original character as rule (no change)
@idx += 1 # increment the index of axiom/production as a side effect
if (context.has_key?(ch)) && (prod[context[ch].idx + idx] == context[ch].cchar)
rule = context[ch].crule # use context sensitive rule
else
rule = no_context[ch] if no_context.has_key?(ch) # context free rule if it exists
end
return rule
end
end
# Test data taken from ABOP
7.times do |i|
grammar = CSGrammar.new("baaaaaa")
grammar.add_rule("b<a", "b") # context sensitive rule replace a when preceded by b
grammar.add_rule("b", "a")
result = grammar.generate(i)
print result << "\n"
end
Test Output: baaaaaa abaaaaa aabaaaa aaabaaa aaaabaa aaaaaba aaaaaab
Note the way b 'travels' from left to right through the production string in the test output.
Reference:
The Algorithmic Beauty of Plants
Przemyslaw Prusinkiewicz
Aristid Lindenmayer
Labels:
context sensitive,
l-system,
ruby,
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