Created
December 31, 2009 08:50
-
-
Save monkstone/266673 to your computer and use it in GitHub Desktop.
Stochastic LSystem Plants in ruby processing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
######################################################## | |
# stochastic_test.rb | |
# | |
# Lindenmayer System in ruby-processing by Martin Prout | |
######################################################## | |
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 |item, 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment