Ruby練習四日目(離散ボロノイ図)

離散ボロノイ図を全探索法を使って作成してみる。全探索法とは、すべての離散点においてどの母点にもっとも近いかを計算する方法。一番単純な方法かつ、計算量の多い方法。

require "digest/md5"
require "cairo"

#
# ボロノイ図を描画する(全探索法)
#
class VoronoiDiagram

    def initialize(width, height)
        @width   = width
        @height  = height
        @context = Cairo::Context.new(
                        Cairo::ImageSurface.new(@width, @height)
                        )
    end

    #=== ボロノイ図を作成
    def draw(kernel_points)
        # 離散点の初期化
        @dp = Array.new(@height) {
                    Array.new(@width) { nil }
                    }

        #
        # 各離散点から最も距離が近い母点の添字を記録する
        #
        @height.times { |y|
            @width.times { |x|
                min_dist = Float::MAX
                kernel_points.each_with_index { |point, i|
                    dist = distance([x, y], point)
                    if (dist < min_dist)
                        min_dist = dist
                        @dp[y][x] = i
                    end
                }
            }
        }

        #
        # 描画
        #
        @context.set_source_rgb(1, 1, 1)          # 色:白
        @context.rectangle(0, 0, @width, @height) # 領域全てをパスで囲む
        @context.fill                             # パスの内側を塗りつぶす

        set_colors(kernel_points)

        # 離散点を描画
        @dp.each_with_index { |line, y|
            line.each_with_index { |cell, x|
                @context.set_source_rgb(
                            @colors[cell][0],
                            @colors[cell][1],
                            @colors[cell][2]
                            )
                @context.rectangle(x, y, 1, 1)
                @context.fill
            }
        }

        # 母点を描画
        @context.set_source_rgb(0, 0, 0)
        kernel_points.each { |point|
            @context.circle(point[0], point[1], 1)
            @context.fill
        }

        @context.target.write_to_png("voronoi.png")
    end

    #=== 領域の色を決定
    def set_colors(points)
        @colors = []
        points.each { |point|
            r = Digest::MD5.hexdigest(point[0].to_s).to_i(16) % 101 
            g = Digest::MD5.hexdigest(point[1].to_s).to_i(16) % 101
            b = Digest::MD5.hexdigest(point.to_s).to_i(16)    % 101
            @colors << [r / 100.0, g / 100.0, b / 100.0]
        }
    end

    #=== 二点間の距離を求める
    def distance(point1, point2)
        dx = point2[0] - point1[0]
        dy = point2[1] - point1[1]
        Math.sqrt(dx * dx + dy * dy)
    end
end

# テスト
points =  DATA.readlines.map { |item|
                    item.chomp.split(/,/).map { |i| i.to_i }
                    }
voronoi = VoronoiDiagram.new(180, 180)
voronoi.draw(points)
__END__
10,12
10,137
91,99
90,28
123,120
160,78
170,170
感想
  • Ruby の一番の特徴はブロックかな。この例だとイテレータとしてしかつかっていないけど、他にも色々できるようで。
  • Array.each { |item| ... } の item を書き換えても、Array 本体の要素が変更されるわけではないのね。そういう場合は map か collect を使えということか。
  • 色を決定するロジックがいまいち。母点の数が増えていっても隣接する領域の色が似たような色にならなければいいんだけど。
  • だんだんRubyの練習から離れてきてる気がする。