Ruby で簡単なタートルグラフィックスを実装して、いろいろ再帰曲線を描いてみました。描画には自作の Gem 'oekaki' を使っています。
oekaki | RubyGems.org | your community gem host
GTK+でお絵かきしてみた(Ruby) - Camera Obscura
※追記
Gem化しました。
Gem 'oekaki' にタートルグラフィックスを追加 - Camera Obscura
再帰曲線を描くなら、L-system も参考になると思います。これも実装しました。
再帰曲線を描く言語「L-system」を Ruby で実装した - Camera Obscura
タートルグラフィックスの実装はこんな感じです。
turtle.rb(Gist)
class Turtle
def initialize(width, height, ob)
@width, @height = width, height
@pen_po = Vector[0, 0]
@pen = ob
@dir = Vector[1, 0]
@color = [65535, 65535, 65535]
end
attr_accessor :pen_po, :dir
def left(deg)
θ = PI * deg / 180
@dir = Matrix[[cos(θ), -sin(θ)], [sin(θ), cos(θ)]] * @dir
end
def right(deg)
left(-deg)
end
def forward(length, draw = true)
next_po = @pen_po + @dir * length
if draw
@pen.color(*@color)
@pen.line(@width / 2 + next_po[0], @height / 2 - next_po[1],
@width / 2 + @pen_po[0], @height / 2 - @pen_po[1])
end
@pen_po = next_po
end
def back(length)
forward(-length, false)
end
def color(r, g, b)
@color = [r, g, b]
@pen.color(*@color)
end
def circle(radius, fill = false)
@pen.color(*@color)
@pen.circle(fill, @width / 2 + @pen_po[0], @height / 2 - @pen_po[1], radius)
end
def move(x, y)
@pen_po = Vector[x, y]
end
end
特にむずかしいところはないと思います。なお、内部の座標 @pen_po
は原点がキャンバスの中心で、(x, y) 座標の向きは数学の慣例どおり(つまり、y 座標は上がプラス方向)です。メソッド left(), right() は向きの回転、forward(length, draw) は長さ length だけ前進(draw が false なら位置が移動するだけで描画されない)、circle(radius, fill) はペンの位置に半径 radius の円を描く(fill が true ならば内部を満たす)、move() はその座標へ移動、back(length) は length だけ戻る(描画はされない)。
Gem 'oekaki' が内部で require 'matrix', include Math をしているので、行列・ベクトル演算と Math モジュールがそのまま使えます。Turtle.new(w, h, self)
は draw メソッドなどのブロック内で行って下さい。(インスタンスはブロックの外へ持ちだしてもかまいません。)
C曲線。Gist。
require 'oekaki'
require_relative 'turtle'
Width, Height = 600, 400
Oekaki.app width: Width, height: Height, title: "C curve" do
draw do
clear
t = Turtle.new(Width, Height, self)
t.move(-130, -100)
t.color(0, 65535, 0)
ratio = sqrt(2) / 2
drawing = lambda do |length, depth|
if depth.zero?
t.forward(length)
else
t.left(45)
drawing[length * ratio, depth - 1]
t.right(90)
drawing[length * ratio, depth - 1]
t.left(45)
end
end
drawing[260.0, 10]
end
end
シェルピンスキー曲線。Gist。
require 'oekaki'
require_relative 'turtle'
Width, Height = 600, 600
Oekaki.app width: Width, height: Height, title: "Sierpinski curve" do
draw do
clear
depth = 5
l = 550
num = 2 ** (depth - 1)
t = Turtle.new(Width, Height, self)
t.color(0xffff, 0, 0xffff)
step = l / ((2 * sqrt(2) + 1) * num + num - 1)
t.move(-l / 2 + step / sqrt(2), l / 2)
drawing = lambda do |depth, angle = 45|
if depth.zero?
t.forward(step)
else
t.right(angle)
drawing[depth - 1, -angle]
t.left(angle)
t.forward(step)
t.left(angle)
drawing[depth - 1, -angle]
t.right(angle)
end
end
4.times do
drawing[2 * depth - 1]
t.right(45)
t.forward(step)
t.right(45)
end
end
end
コッホ曲線。Gist。
require 'oekaki'
require_relative 'turtle'
Width, Height = 600, 600
Oekaki.app width: Width, height: Height, title: "Koch curve" do
draw do
clear
l = 500
t = Turtle.new(Width, Height, self)
t.color(0, 65535, 65535)
t.move(l / 2, -150)
t.dir = Vector[-1, 0]
drawing = lambda do |length, depth|
if depth.zero?
t.forward(length)
else
drawing[length / 3, depth - 1]
t.left(60)
drawing[length / 3, depth - 1]
t.right(120)
drawing[length / 3, depth - 1]
t.left(60)
drawing[length / 3, depth - 1]
end
end
3.times do
drawing[l, 3]
t.right(120)
end
end
end
ヒルベルト曲線。Gist。
require 'oekaki'
require_relative 'turtle'
Width, Height = 600, 600
Oekaki.app width: Width, height: Height, title: "Hilbert curve" do
draw do
clear
depth = 5
l = 500
t = Turtle.new(Width, Height, self)
t.color(0xffff, 0x7dff, 0)
t.move(-l / 2, l / 2)
step = l / (2 ** depth - 1)
drawing = lambda do |depth, angle|
if depth.zero?
return
else
t.right(angle)
drawing[depth - 1, -angle]
t.forward(step)
t.left(angle)
drawing[depth - 1, angle]
t.forward(step)
drawing[depth - 1, angle]
t.left(angle)
t.forward(step)
drawing[depth - 1, -angle]
t.right(angle)
end
end
drawing[depth, 90]
end
end
ドラゴン曲線。Gist。
require 'oekaki'
require_relative 'turtle'
Width, Height = 600, 450
Oekaki.app width: Width, height: Height, title: "Dragon curve" do
draw do
clear
depth = 12
l = 350
t = Turtle.new(Width, Height, self)
t.color(0xf0ff, 0xffff, 0xffff)
t.move(-l / 2, 50)
drawing = lambda do |length, depth|
if depth.zero?
t.forward(length)
else
len = length / sqrt(2)
po1 = t.pen_po
t.forward(length, false)
po2, t.pen_po = t.pen_po, po1
t.right(45)
drawing[len, depth - 1]
t.pen_po = po2
t.right(90)
drawing[len, depth - 1]
t.pen_po = po2
t.left(135)
end
end
drawing[l, depth]
end
end
参考にしたサイト。(ありがとうございます!)
再帰プログラムによるフラクタル図形の描画:CodeZine(コードジン)
再帰曲線で遊ぼう
Pythonとタートルグラフィックスによる(再帰)プログラミング教育処方案 - Read -> Blog
特にいちばん下のブログ記事がすごいです。せっかくなので出来るだけ自力でやりました^^;
追加
このサイトの描画例を Python から移植しました。ありがとうございます!
格子曲線。Gist。
require 'oekaki'
require_relative 'turtle'
Width, Height = 400, 400
Oekaki.app width: Width, height: Height, title: "Grate curve" do
draw do
clear(color(0xffff, 0x7dff, 0))
t = Turtle.new(Width, Height, self)
t.color(0, 0, 0)
t.move(-160, 150)
two = lambda do |a, c, w|
return if c <= 1
t.right(a)
t.forward(1)
t.right(a)
t.forward(w)
t.left(a)
t.forward(1) if c > 1
t.left(a)
t.forward(w)
two[a, c - 2, w]
end
square = lambda do |a, h, w|
t.forward(w)
two[a, h - 1, w]
end
grate = lambda do |n, a, w, h|
if n.zero?
square[a, h, w]
else
t.right(a)
grate[n - 1, -a, h / 4, w]
t.forward(h / 8)
grate[n - 1, a, h / 4, w]
t.forward(h / 8)
grate[n - 1, -a, h / 4, w]
t.left(a)
end
end
grate[4, 90, 320, 320]
end
end
クヌース曲線。Gist。
require 'oekaki'
require_relative 'turtle'
Width, Height = 600, 350
Oekaki.app width: Width, height: Height, title: "Knuth curve" do
draw do
clear
t = Turtle.new(Width, Height, self)
t.color(0xf0ff, 0xffff, 0xffff)
t.move(250, -100)
t.left(180)
step = 8
drawing = lambda do |depth, a, tn|
if depth.zero?
t.right(45 + tn)
t.forward(step)
t.left(45 + tn)
else
t.right(2 * tn + a)
drawing[depth - 1, 2 * tn, -tn]
t.right(45 - 3 * tn - a)
t.forward(step)
t.left(45 - tn + a)
drawing[depth - 1, 0, -tn]
t.right(a)
end
end
drawing[9, -90, 45]
end
end
まだコードの意味はよくわかっていないのですが(汗)。よく考えてみたいと思います。