Ruby練習二日目(コッホ曲線)

今日は、コッホ曲線を描くプログラムを作ってみようと思います。理由は特にありません、思いつきです。はじめは曲がる点を求めるだけのプログラムを考えていましたが、どうせなら目に見えるようにしようと思い、描いたコッホ曲線を画像に出力する処理まで作成しました。画像の描画には cairo を使用しました。と。

require 'cairo'

#
# コッホ曲線の描画クラス
#
class KochCurve
    COS = Math.cos(Math::PI / 3)
    SIN = Math.sin(Math::PI / 3)

    #=== コンストラクタ
    #
    #_width_ :: 画像幅
    #_height_ :: 画像高
    #
    def initialize(width, height)
        @width   = width
        @height  = height
        surface  = Cairo::ImageSurface.new(@width, @height)
        @context = Cairo::Context.new(surface)

        init_context    
    end

    #=== 描画領域の初期化
    #
    def init_context
        # 背景の設定
        @context.set_source_rgb(1, 1, 1)          # 色:白
        @context.rectangle(0, 0, @width, @height) # 領域全てをパスで囲む
        @context.fill                             # パスの内側を塗りつぶす

        # 線の設定
        @context.set_source_rgb(0, 0, 0) # 黒
        @context.set_line_width(1.0)     # 太さ
    end

    #=== 一辺のコッホ曲線を描画
    #
    #_x1_ :: 始点x座標
    #_y1_ :: 始点y座標
    #_x2_ :: 終点x座標
    #_y2_ :: 終点y座標
    #_dim_ :: 分割回数
    #
    def koch_curve(x1, y1, x2, y2, dim)
        @context.stroke {
            @context.move_to(x1,y1)
            _koch_curve(x1, y1, x2, y2, dim)
        }
    end

    #=== コッホ曲線のロジック
    #
    #_x1_ :: 始点x座標
    #_y1_ :: 始点y座標
    #_x2_ :: 終点x座標
    #_y2_ :: 終点y座標
    #_dim_ :: 分割回数
    #
    def _koch_curve(x1, y1, x2, y2, dim)
        if dim == 1
            @context.line_to(x2, y2)
        else
            x3 = (2 * x1 + x2) / 3
            y3 = (2 * y1 + y2) / 3
            x5 = (x1 + 2 * x2) / 3
            y5 = (y1 + 2 * y2) / 3
            x4 = x3 + (x5 - x3) * COS + (y5 - y3) * SIN
            y4 = y3 - (x5 - x3) * SIN + (y5 - y3) * COS

            dim -= 1
            _koch_curve(x1, y1, x3, y3, dim)
            _koch_curve(x3, y3, x4, y4, dim)
            _koch_curve(x4, y4, x5, y5, dim)
            _koch_curve(x5, y5, x2, y2, dim)
        end
    end

    #===ファイルへの出力
    #
    #_fname_ :: ファイル名
    #
    def save(fname)
        @context.target.write_to_png(fname)

        # 保存したら初期化する
        init_context
   end
end

使い方はこんな感じ

koch = KochCurve.new(180, 180)     # 幅180px,高さ180pxの画像
koch.koch_curve(0, 90, 180, 90, 3) # 画像の中心(横向き)に線を描く
koch.save("koch.png")              # koch.png というファイルに出力

座標の計算をすれば、コッホ雪片も。下の例だと、右回りに点をトレースします。この場合、内側に伸びていく雪片が描けます。外側に伸びていく雪片にする場合は、左回りにすればOKです。

koch = KochCurve.new(180, 180)
# コッホ曲線を3つ合わせたコッホ雪片から、5つ合わせた画像を作成
for i in 3..5
    x1  = 40                       # 描画開始座標x 適当に調整。
    y1  = 30                       # 描画開始座標y 
    len = 90                       # 線分(?)の長さ
    rad = (Math::PI * (i - 2)) / i # 正n角形の1つの内角
    i.times { |j|
        x2 = x1 + len * Math.cos(rad - (j * (Math::PI - rad)))
        y2 = y1 + len * Math.sin(rad - (j * (Math::PI - rad)))
        koch.koch_curve(x1, y1, x2, y2, 4)
        x1 = x2
        y1 = y2
    }
    koch.save("koch" + i.to_s + ".png")
end

でき上がった画像。これに機能を加えるなら、真ん中に描画する機能かな(トリミングする方が早そうだけど)。

感想
  • はじめて標準添付以外のライブラリを使用した。ライブラリを使わずに、これと同じプログラムを書けといわれたら、作る気をなくしていただろうな。それもこれも皆様のおかげです。
  • Ruby の練習というよりも、sin, cons の勉強って感じ。日頃使わないので、思い出すというよりも再学習。でも、面白かった。