🗻
【フラクタル図形】シェルピンスキーのギャスケットの書き方
class SierpinskiGasket < Base
def call
depth = 5 # 複雑度
radius = wh.min_element * 0.65 # 大きさ
vertex_n = 3 # 頂点数 (固定)
center = V[0.5, 0.66] # 中心 (適当)
vertexes = vertex_n.times.collect do |i|
wh * center + V.from_angle((270 + 360 / vertex_n * i).deg_to_rad) * radius
end
draw(*vertexes, depth)
end
def draw(v0, v1, v2, depth)
if depth <= 0
return
end
v3 = (v0 + v1) / 2 # 右辺の中央
v4 = (v0 + v2) / 2 # 左辺の中央
v5 = (v1 + v2) / 2 # 山の最深部
# 中央にある三角形に適用するのではない
# draw(v3, v4, v5, depth - 1)
draw(v0, v3, v4, depth - 1) # 上
draw(v1, v3, v5, depth - 1) # 左
draw(v2, v4, v5, depth - 1) # 右
triangle(v0, v1, v2)
snapshot
end
# 正三角形の高さは 辺 * sin(60度) で求まる
def canvas_wh
V[super.x, super.x * Math.sin(2 * Math::PI * 60 / 360)]
end
end
アルゴリズム
- △の3辺のそれぞれの中央の点を求める
- その3点を結ぶと逆三角形ができる
- その逆三角形を除いたときにできる3つの三角形を再分割する
具体的に書くと
- △の頂点を v0, v1, v2 とする
- v0→v1 と v1→v2 と v2→v0 の間の点を求める
- この点を順に v3, v4, v5 とする
- v0→v3→v4 と v1→v3→v5 と v2→v4→v5 の三角形を再度分割する
ここで勘違いしやすいというか自分が勘違いしていたのだけど、真ん中にできた逆三角形に対して再分割するのではない。真ん中にできた逆三角形を除いたときにできる3つの三角形を再分割する。そこさえ間違えなければ、とくに難しいところはない。
共通コード
require "#{__dir__}/../../物理/ベクトル/vec2"
require "rmagick"
include Magick
class Base
include Math
def call
end
def write(path)
layer.write(path)
puts path
open path
end
def animation_write(path, delay: 2)
av = image_list.optimize_layers(Magick::OptimizeLayer)
av.delay = 100.0 / 60 * delay
av.write(path)
puts path
open path
end
private
def canvas_wh
V[800, (800 / 1.618033988749895).to_i]
end
def wh
canvas_wh
end
def title
self.class.name.underscore
end
def snapshot
image_list << layer.dup
end
def snapshot_counter
@snapshot_counter ||= 0
@snapshot_counter += 1
end
def open(path)
system "open -a 'Google Chrome' #{path}"
end
def layer
@layer ||= Image.new(*canvas_wh) do |e|
e.background_color = background_color
end
end
def image_list
@image_list ||= ImageList.new
end
def background_color
"white"
end
def foreground_color
"grey30"
end
def line_width
2
end
def color_from(iter)
r = iter % 32 * 8
b = iter % 8 * 32
g = iter % 16 * 16
"#%02x%02x%02x" % [r, g, b]
end
def draw_context
g = Draw.new
yield g
g.draw(layer)
end
def line(v0, v1)
draw_context do |g|
g.stroke_width(line_width)
g.stroke(foreground_color)
g.line(*v0, *v1)
end
end
def triangle(v0, v1, v2)
draw_context do |g|
g.fill("transparent")
g.stroke_width(line_width)
g.stroke(foreground_color)
g.polygon(*v0, *v1, *v2)
end
end
def pixel(v0, color)
draw_context do |g|
g.fill(color)
g.rectangle(*v0, *v0)
end
end
def rectangle(v0, v1, color: foreground_color)
draw_context do |g|
g.fill(color)
g.rectangle(*v0, *v1)
end
end
def rectangle_border(v0, v1)
draw_context do |g|
g.fill("transparent")
g.stroke_width(1)
g.stroke(foreground_color)
g.rectangle(*v0, *v1)
end
end
end
if $0 == __FILE__
require "rspec/autorun"
RSpec.configure do |config|
config.expect_with :test_unit
end
describe do
it "works" do
end
end
end
# >> .
# >>
# >> Finished in 0.00282 seconds (files took 0.08633 seconds to load)
# >> 1 example, 0 failures
# >>
Discussion