I've finally got round to reading "Fractals Everywhere" by Michael F. Barnsley. The math is not overly complicated just a bit unfamiliar. Anyway I'm sort of inspired by some the transforms in the book, to see what I could come up with. I'm particularly attracted by the idea of projecting a 2D fractal into a 3D dimensional space. There are possibly too many different ways of do this (including paper modelling, something I'd not considered before I saw it today over on the processing discourse). My first approach will be to produce a data set for a regular 2D fractal, and thereafter apply some rules to create a deformation of the pattern. To this end I've re-worked my snake-kolam to create a data set (an array of points). The first transformation rules I have created (see ScalingTool class) merely scale and center the fractal. Here is the code:-
##
# Lindenmayer System in ruby-processing by Martin Prout
###
class Kolam_Test < Processing::App
load_libraries 'kolam'
attr_reader :kolam
def setup
size 500, 500
@kolam = Kolam.new
kolam.create_grammar 3 # create grammar from rules
kolam.translate # translate grammar to points
no_loop
end
def draw
background 0
kolam.render width, height # adjust points to fit frame & render
end
end
############################
# library/kolam/kolam.rb
# Non-stochastic grammar
# 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
############################
# snake kolam using l-systems
############################
BORDER = 10 # global border constant
XPOS = 0 # global point array constants
YPOS = 1
class Kolam
include Processing::Proxy
attr_accessor :axiom, :xpos, :ypos, :grammar, :production, :draw_length, :points
ANGLE = 2
DELTA = (Math::PI/180) * 90.0 # convert degrees to radians using ruby
def initialize
@axiom = "FX+F+FX+F"
@grammar = Grammar.new(axiom)
grammar.add_rule("X", "X-F-F+FX+F+FX-F-F+FX")
@theta = DELTA
@points = [[0, 0]] # initialize points array with first point
@draw_length = 1.0
@production = axiom
@xpos = 0
@ypos = 0
end
def translate # NB not using processing affine transforms here
turtle = [xpos, ypos, 0.0]
production.scan(/./).each do |element|
case element
when 'F'
turtle = store_line(turtle, draw_length)
when '+'
turtle[ANGLE] += DELTA
when '-'
turtle[ANGLE] -= DELTA
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
def render(width, height)
st = ScalingTool.new width, height, points
data = st.scale_to_fit
no_fill
stroke(0, 255, 0)
stroke_width(2)
begin_shape
data.each do |point|
vertex(point[XPOS], point[YPOS])
end
end_shape
end
##############################
# create grammar from axiom and rules
# leave scaling & postioning to render
##############################
def create_grammar(gen)
@production = @grammar.generate gen
end
private
######################################################
# calculate and store line using current turtle and length parameters
# returns a turtle corresponding to the new position
######################################################
def store_line(turtle, length)
new_xpos = turtle[XPOS] + length * Math.cos(turtle[ANGLE])
new_ypos = turtle[YPOS] + length * Math.sin(turtle[ANGLE])
*point = new_xpos, new_ypos # collect coordinates
@points.push(point)
*turtle = new_xpos, new_ypos, turtle[ANGLE] % (Math::PI * 2) # collect coordinates & angle
end
end
###################################
# scaling tool, scale and center fractal
###################################
class ScalingTool
attr_reader :max_height, :max_width, :lowest_y, :lowest_x, :highest_y, :highest_x, :raw_data, :scale
def initialize max_width, max_height, data = []
@max_width = max_width - BORDER
@max_height = max_height - BORDER
@raw_data = data
@lowest_x = 0
@lowest_y = 0
@highest_x = 0
@highest_y = 0
@scale = 1
end
def scale_to_fit
processed = raw_data
scale = calculate_scale_factor
processed.each do |item|
item[XPOS] = scale * (item[XPOS] - lowest_x) + BORDER/2
item[YPOS] = scale * (item[YPOS] - lowest_y) + BORDER/2
end
return processed
end
private
def calculate_x_range
@raw_data.each do |item|
@lowest_x = item[XPOS] unless lowest_x < item[XPOS]
@highest_x = item[XPOS] unless highest_x > item[XPOS]
end
highest_x - lowest_x
end
def calculate_y_range
@raw_data.each do |item|
@lowest_y = item[YPOS] unless lowest_y < item[YPOS]
@highest_y = item[YPOS] unless highest_y > item[YPOS]
end
highest_y - lowest_y
end
#################################################
# Returns the smallest of width or height factors
# as side effect stores lowest x and y values
#################################################
def calculate_scale_factor
scale_x = (max_height * 1.0)/calculate_x_range
scale_y = (max_width * 1.0)/calculate_y_range
(scale_x < scale_y) ? scale_x : scale_y
end
end
Experiments with ruby-processing (processing-2.2.1) and JRubyArt for processing-3.0
Monday 8 March 2010
Subscribe to:
Post Comments (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
For those wanting to do 3D stuff with Ruby Processing, I also have a few screencasts up on YouTube, as well as some F/OSS code available on GitHub. http://www.prestonlee.com/2010/05/17/3d-osx-applications-with-ruby-processing-screencast/
ReplyDeleteYou made two identical comments, so I deleted one.
ReplyDeleteProbably the wrong on as this looks to be displaced from where you wanted to comment. Comment again where you wanted to and I'll put it right. I had to control post because of a rash of exclusively Chinese spam.