Skip to content

Instantly share code, notes, and snippets.

@JoshCheek
Last active November 23, 2016 23:52
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save JoshCheek/1133acd9260e6da2e8dfffd7598b03bb to your computer and use it in GitHub Desktop.
LSystems: A tour
Heighway dragon
https://en.wikipedia.org/w/index.php?title=L-system&oldid=748420339#Example_6:_Dragon_curve
Fractal plant
https://en.wikipedia.org/w/index.php?title=L-system&oldid=748420339#Example_7:_Fractal_plant
Sierpinski Gasket
https://en.wikipedia.org/wiki/L-system#Example_5:_Sierpinski_triangle
Koch Curve
https://en.wikipedia.org/wiki/L-system#Example_4:_Koch_curve
Not Algae
Uhhhh, IDK, I made this one up based on Algae,
which doesn't have a visual representation
https://en.wikipedia.org/wiki/L-system#Example_1:_Algae
but at some point it'd diverged so much that I stopped trying
to be faithful to it and just had fun instead :)
Hilbert Curve!! <3 <3 <3
https://en.wikipedia.org/wiki/Hilbert_curve#Representation_as_Lindenmayer_system
Levy C Curve
https://en.wikipedia.org/wiki/Lévy_C_curve
IDK, I made this one up.
Lets call it "Curly Cute! ^_^" (the emoji is part of its name, dammit!)
Peano-Gosper curve
http://mathforum.org/advanced/robertd/lsys2d.html
Peano curve
http://mathforum.org/advanced/robertd/lsys2d.html
Koch curve
http://mathforum.org/advanced/robertd/lsys2d.html
32 segment curve
http://mathforum.org/advanced/robertd/lsys2d.html
Arrowhead curve (seema an awful lot like the Sierpinski Gasket)
http://mathforum.org/advanced/robertd/lsys2d.html
Hilbert Curve II
http://mathforum.org/advanced/robertd/lsys2d.html
Quadratic Koch island
http://mathforum.org/advanced/robertd/lsys2d.html
Square Curve
http://mathforum.org/advanced/robertd/lsys2d.html
Had to guess the start on this one (along with several others, they don't give the start at that site)
I don't think this one has a name
http://www.cs.unh.edu/~charpov/programming-lsystems.html
http://bulletin.cstug.cz/pdf/2012-1.pdf
I don't think this one has a name
http://www.cs.unh.edu/~charpov/programming-lsystems.html
http://bulletin.cstug.cz/pdf/2012-1.pdf
I don't think this one has a name
http://www.cs.unh.edu/~charpov/programming-lsystems.html
Strawberry Donut
Another one I made up
Sisyphean Staircase
(alt: Mario's Nightmare)
Another one I made up
# encoding: utf-8
# Video: https://vimeo.com/192819855
# Uses version 1.0.0b6 of the graphics gem
# https://rubygems.org/gems/graphics/versions/1.0.0b6
# Running with `rsdl` (a dep of graphics) is nicer than running with `ruby`
# $ rsdl abstract_snowflake_heighway_dragon.rb
# https://en.wikipedia.org/wiki/L-system
LSystem = Module.new
class LSystem::Production
def initialize(start:, rules:)
self.rules, self.cache = rules, [start]
end
def each(depth, &block)
at(depth).each_char(&block)
end
def to_s(depth)
at(depth).dup
end
private
attr_accessor :rules, :cache
def at(depth)
cache[depth] ||= at(depth-1).gsub(replacements, rules)
end
def replacements
@replacements ||= Regexp.new "[#{Regexp.escape rules.keys.join}]"
end
end
class LSystem::Update
def initialize(production:, turtles:, depth:)
self.production = production
self.turtles = turtles
self.depth = depth
end
# all behaviour is accomplished via callbacks on the turtle
def call
update &:save
production.each(depth) do |char|
case char
when 'F', 'G' then update &:forward
when '-' then update &:turn_left
when '+' then update &:turn_right
when "[" then update &:save
when "]" then update &:restore
end
end
update &:restore
end
def update(&command)
turtles.each &command
self
end
private
attr_accessor :production, :turtles, :depth
end
class LSystem::Turtle
attr_accessor :x, :y, , :on_move, :stack, :stride, :∆ø
# Angles are in radians
#
# Interestingly, if we had angle objects, it wouldnt' matter
# eg these would both do the same thing:
# stride*Degrees.new(90).cos
# stride*Radians.new(π/2).cos
def initialize(x, y, ø, ∆ø, stride, &on_move)
self.x = x
self.y = y
self.ø = ø
self.∆ø = ∆ø
self.stride = stride
self.on_move = on_move
self.stack = []
end
def save
stack.push [x, y, ø]
self
end
def restore
self.x, self.y, self.ø = stack.pop
self
end
def turn_left
self.ø += ∆ø
self
end
def turn_right
self.ø -= ∆ø
self
end
def forward
new_x = x + stride*Math.cos(ø)
new_y = y + stride*Math.sin(ø)
on_move.call(x, y, new_x, new_y, ø)
self.x = new_x
self.y = new_y
self
end
end
require 'graphics'
module LSystem
# ===== Useful LSystem integration code =====
# https://en.wikipedia.org/w/index.php?title=L-system&oldid=748420339#Example_7:_Fractal_plant
Production::PLANT = Production.new(
start: "X",
rules: {"F"=>"FF", "X"=>"F-[[X]+X]+F[+FX]-X"},
)
# https://en.wikipedia.org/w/index.php?title=L-system&oldid=748420339#Example_6:_Dragon_curve
Production::HEIGHWAY_DRAGON = Production.new(
start: "-X",
rules: {"X"=>"X+F+Y", "Y"=>"X-F-Y"},
)
def new_turtle(x:, y:, ø:, ∆ø:, stride:, colour:, draw_thick: false)
last = [nil, nil]
Turtle.new x, y, ø, ∆ø, stride do |x1,y1, x2,y2, |
# Make runs of the same angle be the same colour (eg in the tree, they correspond to branches, the branch will be only one colour here)
# The .to_int thing allows RandomColour to not randomly wind up at a different colour for the thicker lines
last = [, color[colour].to_int] if last[0] !=
crnt_colour = last[1]
# crnt_colour = color[colour].to_int
line x1, y1, x2, y2, crnt_colour # original line
if draw_thick
line x1-1, y1, x2-1, y2, crnt_colour # line to the left
line x1+1, y1, x2+1, y2, crnt_colour # line to the right
line x1, y1-1, x2, y2-1, crnt_colour # line below
line x1, y1+1, x2, y2+1, crnt_colour # line above
end
end
end
end
module SimulationHelpers
# ===== Math helpers =====
include Math
def π
PI
end
def deg2rad(degrees)
degrees * π/180
end
# ===== Graphics conveniences & tweaks =====
def initialize(*)
super # args are implicit
# a hack to let us use colour values alongside named colours
color.default_proc = lambda { |h, k| k }
end
def rgba(r, g, b, a=0xFF)
screen.format.map_rgba r, g, b, 0xFF
end
def off_screen?(x, y)
!on_screen?(x, y)
end
def on_screen?(x, y)
0 <= x && x <= w && 0 <= y && y <= h
end
end
class RandomColour
def initialize(*colour_pool)
@colour_pool = colour_pool.flatten
end
def to_int
@colour_pool.sample
end
end
class LSystems < Graphics::Simulation
include LSystem
include SimulationHelpers
def initialize
super 1440, 900, 24
# set up colours
self.bg_color = :espresso_libre_brown
register_color :espresso_libre_brown, 0x2A, 0x21, 0x1C
dark_dragon_green = rgba( 0x44/4 + 3*0x2A/4,
0x99/4 + 3*0x21/4,
0x1C/4 + 3*0x1C/4)
light_dragon_green = rgba(0x44/2 + 0x2A/2,
0x99/2 + 0x21/2,
0x1C/2 + 0x1C/2)
light_brown_1 = rgba(0x6F, 0x61, 0x2C)
light_brown_2 = rgba(0x8F, 0x81, 0x3C)
# Useful variables
self.lsystems = [
# Heighway dragon
Update.new(
production: Production::HEIGHWAY_DRAGON,
depth: 14,
turtles: [new_turtle(
x: 1000,
y: 600,
ø: π/4,
∆ø: π/4,
stride: 4,
draw_thick: true,
colour: RandomColour.new(
[dark_dragon_green] * 20,
light_dragon_green
)
)],
),
# Fractal plant
Update.new(
production: Production::PLANT,
depth: 6,
turtles: [new_turtle(
x: w/2,
y: 75,
ø: π/2,
∆ø: deg2rad(25),
stride: 5,
draw_thick: true,
colour: RandomColour.new(
dark_dragon_green,
light_dragon_green,
light_dragon_green,
light_brown_1,
light_brown_2,
)
)],
),
# Sierpinski Gasket
# https://en.wikipedia.org/wiki/L-system#Example_5:_Sierpinski_triangle
Update.new(
production: Production.new(start: "F-G-G", rules: {"F"=>"F-G+F+G-F", "G"=>"GG"}),
depth: 9,
turtles: [new_turtle(
x: 250,
y: 85,
ø: 0,
∆ø: deg2rad(120),
stride: 1.75,
draw_thick: false,
colour: :white,
)],
),
# Koch Curve
# https://en.wikipedia.org/wiki/L-system#Example_4:_Koch_curve
Update.new(
production: Production.new(start: "F", rules: {"F"=>"F+F-F-F+F"}),
depth: 5,
turtles: [new_turtle(
x: 1375,
y: 150,
ø: π,
∆ø: π/2,
stride: 5.5,
draw_thick: true,
colour: RandomColour.new(:white, :white, :white, light_brown_1, light_brown_2),
)],
),
# Not Algae
# Uhhhh, IDK, I made this one up based on Algae,
# which doesn't have a visual representation
# https://en.wikipedia.org/wiki/L-system#Example_1:_Algae
# but at some point it'd diverged so much that I stopped trying
# to be faithful to it and just had fun instead :)
Update.new(
production: Production.new(start: "F", rules: {'F'=>'[-G][+G]GF', 'G'=>'[-F]F'}),
depth: 9,
turtles: [new_turtle(
x: 300,
y: 150,
ø: π/6,
∆ø: π/6,
stride: 10,
draw_thick: false,
colour: RandomColour.new(light_dragon_green, :green, :white, light_brown_2, light_brown_1),
)],
),
# Hilbert Curve!! <3 <3 <3
# https://en.wikipedia.org/wiki/Hilbert_curve#Representation_as_Lindenmayer_system
Update.new(
production: Production.new(start: "A", rules: {'A'=>'-BF+AFA+FB-', 'B'=>'+AF-BFB-FA+'}),
depth: 7,
turtles: [new_turtle(
x: 400,
y: 100,
ø: 0,
∆ø: π/2,
stride: 6,
draw_thick: false,
colour: RandomColour.new([:white]*20, :yellow),
)],
),
# Levy C Curve
# https://en.wikipedia.org/wiki/Lévy_C_curve
Update.new(
production: Production.new(start: "F", rules: {'F'=> '+F--F+'}),
depth: 14,
turtles: [new_turtle(
x: 400,
y: h-185,
ø: 0,
∆ø: deg2rad(45),
stride: 5,
draw_thick: false,
colour: RandomColour.new(20.times.map { rgba rand(200)+50, rand(200)+50, 0xFF }),
)],
),
# IDK, I made this one up.
# Lets call it "Curly Cute! ^_^" (the emoji is part of its name, dammit!)
Update.new(
production: Production.new(
start: "a",
rules: {
'a' => 'FF+FF+FF+F[+Fa]F+FF+FF+FFb',
'b' => 'FF-FF-FF-F[-Fb]F-FF-FF-FFa',
'F' => 'FF'
}
),
depth: 6,
turtles: [new_turtle(
x: 180,
y: 175,
ø: deg2rad(120),
∆ø: π/6,
stride: 3,
draw_thick: true,
colour: RandomColour.new(20.times.map { rgba rand(200)+50, rand(200)+50, 0xFF }),
)],
),
# Peano-Gosper curve
# http://mathforum.org/advanced/robertd/lsys2d.html
Update.new(
production: Production.new(
start: "Y",
rules: {
'X' => 'X+YF++YF-FX--FXFX-YF+',
'Y' => '-FX+YFYF++YF+FX--FX-Y',
}
),
depth: 5,
turtles: [new_turtle(
x: 550,
y: h-100,
ø: 0,
∆ø: deg2rad(60),
stride: 5,
draw_thick: false,
colour: RandomColour.new(30.times.map { :white }, 10.times.map { rgba rand(100)+50, rand(50)+150, 0xFF }),
)],
),
# Peano curve
# http://mathforum.org/advanced/robertd/lsys2d.html
Update.new(
production: Production.new(start: "F", rules: {'F' => 'F+F-F-F-F+F+F+F-F'}),
depth: 4,
turtles: [new_turtle(
x: w/4,
y: h/2,
ø: 0,
∆ø: deg2rad(90),
stride: 9,
draw_thick: false,
colour: RandomColour.new(10.times.map { rgba rand(255), rand(255), rand(255) }),
)],
),
# Koch curve
# http://mathforum.org/advanced/robertd/lsys2d.html
Update.new(
production: Production.new(start: "F", rules: {'F' => 'F+F--F+F'}),
depth: 5,
turtles: [new_turtle(
x: 100,
y: h-100,
ø: 0,
∆ø: deg2rad(60),
stride: 5,
draw_thick: true,
colour: :white
)],
),
# 32 segment curve
# http://mathforum.org/advanced/robertd/lsys2d.html
Update.new(
production: Production.new(
start: "F+F+F+F",
rules: {'F' => '-F+F-F-F+F+FF-F+F+FF+F-F-FF+FF-FF+F+F-FF-F-F+FF-F-F+F+F-F+'}
),
depth: 2,
turtles: [new_turtle(
x: 500,
y: h-400,
ø: π/6,
∆ø: deg2rad(90),
stride: 6,
draw_thick: true,
colour: RandomColour.new(10.times.map { rgba rand(255), rand(255), rand(255) }),
)],
),
# Arrowhead curve (seema an awful lot like the Sierpinski Gasket)
# http://mathforum.org/advanced/robertd/lsys2d.html
Update.new(
production: Production.new(
start: 'X',
rules: {'X' => 'YF+XF+Y', 'Y' => 'XF-YF-X'}
),
depth: 7,
turtles: [new_turtle(
x: 250,
y: h-50,
ø: 0,
∆ø: deg2rad(60),
stride: 7,
draw_thick: true,
colour: RandomColour.new(10.times.map { rgba 0xFF, rand(100)+100, rand(100) }),
)],
),
# Hilbert Curve II
# http://mathforum.org/advanced/robertd/lsys2d.html
Update.new(
production: Production.new(
start: 'X',
rules: {
'X' => 'XFYFX+F+YFXFY-F-XFYFX',
'Y' => 'YFXFY-F-XFYFX+F+YFXFY',
}
),
depth: 4,
turtles: [new_turtle(
x: 350,
y: h-75,
ø: 0,
∆ø: deg2rad(90),
stride: 9,
draw_thick: true,
colour: :white,
)],
),
# Quadratic Koch island
# http://mathforum.org/advanced/robertd/lsys2d.html
Update.new(
production: Production.new(start: "F+F+F+F", rules: {'F' => 'F-F+F+FFF-F-F+F'}),
depth: 3,
turtles: [new_turtle(
x: 600,
y: h-185,
ø: π/6,
∆ø: deg2rad(90),
stride: 6,
draw_thick: true,
colour: RandomColour.new(10.times.map { rgba rand(255), rand(255), rand(255) }),
)],
),
# Square Curve
# http://mathforum.org/advanced/robertd/lsys2d.html
# Had to guess the start on this one (along with several others, they don't give the start at that site)
Update.new(
production: Production.new(start: "XF+F+XF+F+", rules: {'X' => 'XF-F+F-XF+F+XF-F+F-X'}),
depth: 5,
turtles: [new_turtle(
x: 400,
y: h/2,
ø: 0,
∆ø: deg2rad(90),
stride: 5,
draw_thick: false,
colour: RandomColour.new(:green, :white, :white, :white),
)],
),
# I don't think this one has a name
# http://www.cs.unh.edu/~charpov/programming-lsystems.html
# http://bulletin.cstug.cz/pdf/2012-1.pdf
Update.new(
production: Production.new(start: "F", rules: {'F' => 'FF-[-F+F+F]+[+F-F-F]'}),
depth: 5,
turtles: [new_turtle(
x: w/2,
y: 200,
ø: deg2rad(75),
∆ø: deg2rad(22.5),
stride: 5,
draw_thick: false,
colour: RandomColour.new(
:green,
:white,
dark_dragon_green,
[light_dragon_green]*10,
light_brown_1,
light_brown_2,
light_brown_2,
light_brown_2,
)
)],
),
# I don't think this one has a name
# http://www.cs.unh.edu/~charpov/programming-lsystems.html
# http://bulletin.cstug.cz/pdf/2012-1.pdf
Update.new(
production: Production.new(start: "F", rules: {'F' => 'F[+F]F[-F][F]'}),
depth: 6,
turtles: [new_turtle(
x: 300,
y: 150,
ø: deg2rad(30),
∆ø: deg2rad(20),
stride: 8,
draw_thick: false,
colour: RandomColour.new(
:green,
:white,
dark_dragon_green,
[light_dragon_green]*10,
light_brown_1,
light_brown_2,
light_brown_2,
light_brown_2,
)
)],
),
# I don't think this one has a name
# http://www.cs.unh.edu/~charpov/programming-lsystems.html
Update.new(
production: Production.new(start: "F", rules: {'F' => 'F[+F]F[-F]F'}),
depth: 5,
turtles: [new_turtle(
x: 100,
y: 150,
ø: deg2rad(20),
∆ø: deg2rad(25),
stride: 5,
draw_thick: false,
colour: RandomColour.new(
:green,
:white,
dark_dragon_green,
[light_dragon_green]*10,
light_brown_1,
light_brown_2,
light_brown_2,
light_brown_2,
)
)],
),
# Strawberry Donut
# Another one I made up
Update.new(
production: Production.new(
start: "F",
rules: {
'F' => '-FF-F++F-FF',
}
),
depth: 6,
turtles: [new_turtle(
x: w/2,
y: h/2,
ø: deg2rad(20),
∆ø: deg2rad(10),
stride: 8,
draw_thick: false,
colour: RandomColour.new(:white, 10.times.map { rgba rand(255), 0, rand(255) }),
)],
),
# Sisyphean Staircase
# (alt: Mario's Nightmare)
# Another one I made up
Update.new(
production: Production.new(
start: "b-b-b-b-b-b-b-b",
rules: {
'b' => 'bFF[--r]',
's' => 'rt',
'r' => 'rFF',
}
),
depth: 50,
turtles: [new_turtle(
x: 550,
y: 85,
ø: 0,
∆ø: deg2rad(45),
stride: 3,
draw_thick: true,
colour: RandomColour.new(:white, 10.times.map { rgba 200+rand(55), 100+rand(75), rand(100) }),
)],
),
]
end
def draw(n)
clear :espresso_libre_brown
steps_per_lsystem = 50
if ((n+1) % steps_per_lsystem) == 0
lsystems.shift
self.done = true if lsystems.empty?
else
lsystems[0].call
end
end
private
attr_accessor :bg_color, :lsystems
end
LSystems.new.run
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment