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
Friday, 12 February 2010
Developing the shapes
From the pentive? fractal it is clearly possible to develop some interesting "textured" shapes, here I produce an approximate circle from a set of rotations.
########################################################
# A Pentive fractal implemented using a
# Lindenmayer System in ruby-processing by Martin Prout
########################################################
require 'pentive'
class Pentive_Test < Processing::App
attr_reader :pentive,:points, :production
def setup
size(500, 500)
@pentive = Pentive.new(0, 0)
@production = pentive.create_grammar(6)
@points = pentive.translate_rules(production)
no_loop()
end
def draw_element()
stroke(255)
points.each do |tmp|
line(*tmp)
end
end
def draw()
background(0)
translate(width/2, height/2)
10.times do
rotate(Math::PI/180 * 36)
draw_element()
end
save_frame("/home/tux/advance.png")
end
end
########################################################
# A Pentive fractal implemented using a
# Lindenmayer System in ruby-processing by Martin Prout
########################################################
require 'pentive'
class Pentive_Test < Processing::App
attr_reader :pentive,:points, :production
def setup
size(500, 500)
@pentive = Pentive.new(0, 0)
@production = pentive.create_grammar(6)
@points = pentive.translate_rules(production)
no_loop()
end
def draw_element()
stroke(255)
points.each do |tmp|
line(*tmp)
end
end
def draw()
background(0)
translate(width/2, height/2)
10.times do
rotate(Math::PI/180 * 36)
draw_element()
end
save_frame("/home/tux/advance.png")
end
end
Labels:
developing shapes,
fractal art
Thursday, 11 February 2010
A Pentive? Fractal
The pentive fractal is another space filling fractal that I found over on a fractint site again for the grammar generator see my Cesàro fractal.
########################################################
# A Pentive fractal implemented using a
# Lindenmayer System in ruby-processing by Martin Prout
########################################################
require 'pentive'
class Pentive_Test < Processing::App
attr_reader :pentive,:points, :production
def setup
size(600, 400)
@pentive = Pentive.new(width/95, height*0.9)
@production = pentive.create_grammar(8)
@points = pentive.translate_rules(production)
no_loop()
end
def draw()
background(0)
stroke(255)
points.each do |tmp|
line(*tmp)
end
end
end
####################################################
# The Pentive? fractal
####################################################
class Pentive
attr_reader :draw_length, :xpos, :ypos, :theta, :axiom, :grammar, :delta
DELTA = Math::PI/180 * 36 # 36 degrees
def initialize xpos, ypos
@axiom = "Q"
@theta = -DELTA
@grammar = Grammar.new(axiom)
grammar.add_rule("F", "")
grammar.add_rule("P","1-FR3+FS1-FU") # abbreviated grammar 1 = two & 3 = four repeats
grammar.add_rule("Q", "FT1+FR3-FS1+")
grammar.add_rule("R", "1+FP3-FQ1+FT")
grammar.add_rule("S", "FU1-FP3+FQ1-")
grammar.add_rule("T", "+FU1-FP+")
grammar.add_rule("U", "-FQ1+FT-")
@draw_length = 12
@xpos = xpos
@ypos = ypos
end
def create_grammar(gen)
grammar.generate(gen)
end
def translate_rules(prod)
repeats = 1
points = [] # An empty array to store lines as an array of points
prod.scan(/./) do |ch|
case(ch)
when "F"
temp = [xpos, ypos, (@xpos += draw_length * Math.cos(theta)), (@ypos += draw_length * Math.sin(theta))]
points.push(temp)
when "+"
@theta += DELTA * repeats
repeats = 1
when "-"
@theta -= DELTA * repeats
repeats = 1
when '1', '3'
repeats += Integer(ch)
when "P", "Q", "R", "S", "T", "U"
else
puts("character '#{ch}' not in grammar")
end
end
return points
end
end
########################################################
# A Pentive fractal implemented using a
# Lindenmayer System in ruby-processing by Martin Prout
########################################################
require 'pentive'
class Pentive_Test < Processing::App
attr_reader :pentive,:points, :production
def setup
size(600, 400)
@pentive = Pentive.new(width/95, height*0.9)
@production = pentive.create_grammar(8)
@points = pentive.translate_rules(production)
no_loop()
end
def draw()
background(0)
stroke(255)
points.each do |tmp|
line(*tmp)
end
end
end
####################################################
# The Pentive? fractal
####################################################
class Pentive
attr_reader :draw_length, :xpos, :ypos, :theta, :axiom, :grammar, :delta
DELTA = Math::PI/180 * 36 # 36 degrees
def initialize xpos, ypos
@axiom = "Q"
@theta = -DELTA
@grammar = Grammar.new(axiom)
grammar.add_rule("F", "")
grammar.add_rule("P","1-FR3+FS1-FU") # abbreviated grammar 1 = two & 3 = four repeats
grammar.add_rule("Q", "FT1+FR3-FS1+")
grammar.add_rule("R", "1+FP3-FQ1+FT")
grammar.add_rule("S", "FU1-FP3+FQ1-")
grammar.add_rule("T", "+FU1-FP+")
grammar.add_rule("U", "-FQ1+FT-")
@draw_length = 12
@xpos = xpos
@ypos = ypos
end
def create_grammar(gen)
grammar.generate(gen)
end
def translate_rules(prod)
repeats = 1
points = [] # An empty array to store lines as an array of points
prod.scan(/./) do |ch|
case(ch)
when "F"
temp = [xpos, ypos, (@xpos += draw_length * Math.cos(theta)), (@ypos += draw_length * Math.sin(theta))]
points.push(temp)
when "+"
@theta += DELTA * repeats
repeats = 1
when "-"
@theta -= DELTA * repeats
repeats = 1
when '1', '3'
repeats += Integer(ch)
when "P", "Q", "R", "S", "T", "U"
else
puts("character '#{ch}' not in grammar")
end
end
return points
end
end
Labels:
l-system fractal,
ruby processing
MPeano Fractal (Traveling Salesman Problem)
Uses my grammar library see the Cesàro fractal, here I included it in the mpeano.rb file, which is why it didn't need to be separately loaded (omitted for brevity).
########################################################
# A MPeano fractal implemented using a
# Lindenmayer System in ruby-processing by Martin Prout
########################################################
require 'mpeano'
class MPeano_Test < Processing::App
attr_reader :mpeano, :points, :production
def setup
size(600, 600)
@mpeano = MPeano.new(width/2, height*0.95)
@production = mpeano.create_grammar(7)
@points = mpeano.translate_rules(production)
no_loop()
end
def draw()
background(0)
stroke(255)
points.each do |tmp|
line(*tmp)
end
end
end
####################################################
# The MPeano fractal has been used to study the
# Euclidean travelling salesman problem
####################################################
class MPeano
attr_reader :draw_length, :xpos, :ypos, :theta, :axiom, :grammar, :delta
def initialize xpos, ypos
@axiom = "XFF--AFF--XFF--AFF"
@delta = Math::PI/4 # 45 degrees
@theta = delta * 2
@grammar = Grammar.new(axiom)
grammar.add_rule("X", "+!X!FF-BQFI-!X!FF+")
grammar.add_rule("F", "")
grammar.add_rule("Y", "FFY")
grammar.add_rule("A", "BQFI")
grammar.add_rule("B", "AFF")
@draw_length = 8
@xpos = xpos
@ypos = ypos
end
def create_grammar(gen)
grammar.generate(gen)
end
def translate_rules(prod)
points = [] # An empty array to store lines as an array of points
prod.scan(/./) do |ch|
case(ch)
when "F"
temp = [xpos, ypos, (@xpos -= draw_length * Math.cos(theta)), (@ypos -= draw_length * Math.sin(theta))]
points.push(temp)
when "+"
@theta += delta
when "-"
@theta -= delta
when "!"
@delta = -delta
when "I"
@draw_length *= 1/Math.sqrt(2)
when "Q"
@draw_length *= Math.sqrt(2)
when "X", "A", "B"
else
puts("character '#{ch}' not in grammar")
end
end
return points
end
end
########################################################
# A MPeano fractal implemented using a
# Lindenmayer System in ruby-processing by Martin Prout
########################################################
require 'mpeano'
class MPeano_Test < Processing::App
attr_reader :mpeano, :points, :production
def setup
size(600, 600)
@mpeano = MPeano.new(width/2, height*0.95)
@production = mpeano.create_grammar(7)
@points = mpeano.translate_rules(production)
no_loop()
end
def draw()
background(0)
stroke(255)
points.each do |tmp|
line(*tmp)
end
end
end
####################################################
# The MPeano fractal has been used to study the
# Euclidean travelling salesman problem
####################################################
class MPeano
attr_reader :draw_length, :xpos, :ypos, :theta, :axiom, :grammar, :delta
def initialize xpos, ypos
@axiom = "XFF--AFF--XFF--AFF"
@delta = Math::PI/4 # 45 degrees
@theta = delta * 2
@grammar = Grammar.new(axiom)
grammar.add_rule("X", "+!X!FF-BQFI-!X!FF+")
grammar.add_rule("F", "")
grammar.add_rule("Y", "FFY")
grammar.add_rule("A", "BQFI")
grammar.add_rule("B", "AFF")
@draw_length = 8
@xpos = xpos
@ypos = ypos
end
def create_grammar(gen)
grammar.generate(gen)
end
def translate_rules(prod)
points = [] # An empty array to store lines as an array of points
prod.scan(/./) do |ch|
case(ch)
when "F"
temp = [xpos, ypos, (@xpos -= draw_length * Math.cos(theta)), (@ypos -= draw_length * Math.sin(theta))]
points.push(temp)
when "+"
@theta += delta
when "-"
@theta -= delta
when "!"
@delta = -delta
when "I"
@draw_length *= 1/Math.sqrt(2)
when "Q"
@draw_length *= Math.sqrt(2)
when "X", "A", "B"
else
puts("character '#{ch}' not in grammar")
end
end
return points
end
end
Labels:
MPeano fractal,
ruby processing
Wednesday, 10 February 2010
DavidTour fractal
For the grammar.rb library see my Cesàro fractal (here it was included in the davidtour.rb file which is why it didn't need to be separately loaded, Grammar code omitted for brevity).
########################################################
# A David Tour fractal implemented using a
# Lindenmayer System in ruby-processing by Martin Prout
########################################################
require 'davidtour'
class David_Test < Processing::App
attr_reader :david, :points, :production
def setup
size(800, 900)
@david = DavidTour.new(width * 0.6, height/4)
@production = david.create_grammar(5)
@points = david.translate_rules(production)
no_loop()
end
def draw()
background(0)
stroke(255)
points.each do |tmp|
line(*tmp)
end
end
end
####################################################
# The DavidTour fractal has been used to study the
# Euclidean travelling salesmam problem
####################################################
class DavidTour
attr_reader :draw_length, :xpos, :ypos, :theta, :axiom, :grammar
DELTA = Math::PI/3 # 60 degrees
def initialize xpos, ypos
@axiom = "FX-XFX-XFX-XFX-XFX-XF"
@theta = 0
@grammar = Grammar.new(axiom)
grammar.add_rule("F", "!F!-F-!F!")
grammar.add_rule("X", "!X")
@draw_length = 15
@xpos = xpos
@ypos = ypos
end
def create_grammar(gen)
@draw_length *= @draw_length * 0.5**gen
grammar.generate(gen)
end
def translate_rules(prod)
swap = false
points = [] # An empty array to store lines as an array of points
prod.scan(/./) do |ch|
case(ch)
when 'F'
temp = [xpos, ypos, (@xpos += draw_length * Math.cos(theta)), (@ypos -= draw_length * Math.sin(theta))]
points.push(temp)
when '+'
@theta += (DELTA)
when '-'
@theta += (swap ? DELTA : -DELTA)
when '!'
swap = !swap
when 'X'
else
puts("character '#{ch}' not in grammar")
end
end
return points
end
end
########################################################
# A David Tour fractal implemented using a
# Lindenmayer System in ruby-processing by Martin Prout
########################################################
require 'davidtour'
class David_Test < Processing::App
attr_reader :david, :points, :production
def setup
size(800, 900)
@david = DavidTour.new(width * 0.6, height/4)
@production = david.create_grammar(5)
@points = david.translate_rules(production)
no_loop()
end
def draw()
background(0)
stroke(255)
points.each do |tmp|
line(*tmp)
end
end
end
####################################################
# The DavidTour fractal has been used to study the
# Euclidean travelling salesmam problem
####################################################
class DavidTour
attr_reader :draw_length, :xpos, :ypos, :theta, :axiom, :grammar
DELTA = Math::PI/3 # 60 degrees
def initialize xpos, ypos
@axiom = "FX-XFX-XFX-XFX-XFX-XF"
@theta = 0
@grammar = Grammar.new(axiom)
grammar.add_rule("F", "!F!-F-!F!")
grammar.add_rule("X", "!X")
@draw_length = 15
@xpos = xpos
@ypos = ypos
end
def create_grammar(gen)
@draw_length *= @draw_length * 0.5**gen
grammar.generate(gen)
end
def translate_rules(prod)
swap = false
points = [] # An empty array to store lines as an array of points
prod.scan(/./) do |ch|
case(ch)
when 'F'
temp = [xpos, ypos, (@xpos += draw_length * Math.cos(theta)), (@ypos -= draw_length * Math.sin(theta))]
points.push(temp)
when '+'
@theta += (DELTA)
when '-'
@theta += (swap ? DELTA : -DELTA)
when '!'
swap = !swap
when 'X'
else
puts("character '#{ch}' not in grammar")
end
end
return points
end
end
Labels:
fractal,
LSystems,
travelling salesman
Tuesday, 2 February 2010
The Best of Both Worlds (combining PeasyCam and control_panel
This ruby processing sketch shows how to get the best of both worlds; the smooth easily setup PeasyCam, that you can use the scroll-wheel to zoom, and mouse to drag, and the possible fine grain control of a control panel. In this example I have initially disabled mouse control; use the button to toggle mouse control on or off. Use the control panel slider to set off the rotation or the freeze! button (to stop the rotation). If you only want fine grain rotation (cf. continuous rotation with the slider) then you will need get the camera to remember state (I believe Quark has done this in vanilla processing). If you are really keen you could also implement a slider to control the zoom of the peasy cam (in place of the mouse-wheel).
load_libraries 'PeasyCam', 'control_panel'
import 'peasy'
attr_reader :cam, :x_rotate, :y_rotate, :z_rotate, :controlled
def setup()
size(200, 200, P3D)
configure_panel()
@controlled = true
configure_camera()
end
def configure_camera()
@cam = PeasyCam.new(self, 100)
cam.set_minimum_distance(50)
cam.set_maximum_distance(500)
mouse_control()
end
def configure_panel()
control_panel do |c|
c.slider(:x_rotate, -1.0..1.0, 0.0)
c.slider(:y_rotate, -1.0..1.0, 0.0)
c.slider(:z_rotate, -1.0..1.0, 0.0)
c.button(:freeze!)
c.button(:mouse_control)
end
end
def freeze!()
@x_rotate = 0.0
@y_rotate = 0.0
@z_rotate = 0.0
end
def mouse_control() # toggle mouse controlled camera
cam.set_mouse_controlled(!controlled)
@controlled = !controlled
end
def draw()
cam.rotate_x(x_rotate/100)
cam.rotate_y(y_rotate/100)
cam.rotate_z(z_rotate/100)
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
load_libraries 'PeasyCam', 'control_panel'
import 'peasy'
attr_reader :cam, :x_rotate, :y_rotate, :z_rotate, :controlled
def setup()
size(200, 200, P3D)
configure_panel()
@controlled = true
configure_camera()
end
def configure_camera()
@cam = PeasyCam.new(self, 100)
cam.set_minimum_distance(50)
cam.set_maximum_distance(500)
mouse_control()
end
def configure_panel()
control_panel do |c|
c.slider(:x_rotate, -1.0..1.0, 0.0)
c.slider(:y_rotate, -1.0..1.0, 0.0)
c.slider(:z_rotate, -1.0..1.0, 0.0)
c.button(:freeze!)
c.button(:mouse_control)
end
end
def freeze!()
@x_rotate = 0.0
@y_rotate = 0.0
@z_rotate = 0.0
end
def mouse_control() # toggle mouse controlled camera
cam.set_mouse_controlled(!controlled)
@controlled = !controlled
end
def draw()
cam.rotate_x(x_rotate/100)
cam.rotate_y(y_rotate/100)
cam.rotate_z(z_rotate/100)
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
Labels:
control panel,
PeasyCam library,
ruby processing
Sunday, 31 January 2010
Hello Peasy in ruby processing (a simple PeasyCam example)
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
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
Labels:
PeasyCam library,
ruby processing
Subscribe to:
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






