Last active
November 23, 2016 23:52
Star
You must be signed in to star a gist
LSystems: A tour
This file contains 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
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 |
This file contains 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
# 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