ラスタライザを作る

もう年末なのだが、ちょっと前に作ってみたラスタライザの話。といってもすごいしょぼいもので、とりあえず太さを指定した直線をDXRubyのImageに描画するというもの。

ようするに三角形2個で四角を描いているだけなのだが、それだったらImageのメソッドでできるんじゃね?って感じで、じゃあなんでこれを作ったのかというと、NanoVGみたいなベクタグラフィックスを作ってみたくて、グラデーションとかやるのに自前で描画する必要があって、とりあえず初期型という状態、なわけだ。
まあ、Rubyでベクタグラフィックス作ってなんかいいことあるのかっつーとよくわからんのだけども、そもそも趣味が作ってみたいものを作る、というものなわけで、作ってみた結果についてはどうでもいいのである。
ちなみにこのコードのキモはエッジファンクションというやつで、ラスタライザの基本となる古典的な判定計算である。高速化は色々とできる余地はあるがとりあえず普通に結果が見れるのでいずれ遅いと思ったときに直す。

require 'dxruby'

# ラスタライズ結果を見やすく描画する
module Viewer
  def self.draw(image, triangles)
    s = 400.0 / image.width
    Window.mag_filter = TEXF_POINT
    Window.draw_ex(50, 50, image, scale_x:s, scale_y:s, center_x:0, center_y:0)
    image.width.times do |c|
      Window.draw_line(c * s + 50, 50, c * s + 50, image.width * s + 50, [100, 100, 100])
      Window.draw_line(50, c * s + 50, image.width * s + 50, c * s + 50, [100, 100, 100])
    end
    
    triangles.each do |ary|
      x1, y1, x2, y2, x3, y3 = *ary
      Window.draw_line(x1 * s + 50, y1 * s + 50, x2 * s + 50, y2 * s + 50, C_RED)
      Window.draw_line(x2 * s + 50, y2 * s + 50, x3 * s + 50, y3 * s + 50, C_RED)
      Window.draw_line(x3 * s + 50, y3 * s + 50, x1 * s + 50, y1 * s + 50, C_RED)
    end
  end
end

class OreVG
  attr_accessor :image, :triangles

  def initialize(l)
    @image = Image.new(l, l, C_WHITE)
    @triangles = []
  end

  # EdgeFunction
  # E(x, y) = (x - X) * dy - (y - Y) * dx
  # E(x, y) <= 0で中と判定。3つのエッジの内側であれば塗る。
  def rasterize(x1, y1, x2, y2, x3, y3)
    dx12 = x2 - x1
    dy12 = y2 - y1
    dx23 = x3 - x2
    dy23 = y3 - y2
    dx31 = x1 - x3
    dy31 = y1 - y3

    # 基点。ピクセルの中心で計算する。
    e1 = (0.5 - x1) * dy12 - (0.5 - y1) * dx12
    e2 = (0.5 - x2) * dy23 - (0.5 - y2) * dx23
    e3 = (0.5 - x3) * dy31 - (0.5 - y3) * dx31

    @image.height.times do |y|
      et1 = e1
      et2 = e2
      et3 = e3
      @image.width.times do |x|
        yield x, y if et1 <= 0 and et2 <= 0 and et3 <= 0
        et1 += dy12
        et2 += dy23
        et3 += dy31
      end
      e1 -= dx12
      e2 -= dx23
      e3 -= dx31
    end
  end

  def triangle(x1, y1, x2, y2, x3, y3)
    self.rasterize(x1, y1, x2, y2, x3, y3) do |x, y|
      @image[x,y] = [100, 100, 255]
    end
    @triangles << [x1, y1, x2, y2, x3, y3]
  end

  def line(x1, y1, x2, y2, w)
    dx = x2 - x1
    dy = y2 - y1
    len = Math.sqrt(dx * dx + dy * dy)
    dmx = dx / len
    dmy = dy / len

    tx1 = x1 + dmy * w * 0.5
    ty1 = y1 - dmx * w * 0.5
    tx2 = x1 - dmy * w * 0.5
    ty2 = y1 + dmx * w * 0.5
    tx3 = x2 + dmy * w * 0.5
    ty3 = y2 - dmx * w * 0.5
    tx4 = x2 - dmy * w * 0.5
    ty4 = y2 + dmx * w * 0.5
    self.triangle(tx1, ty1, tx3, ty3, tx2, ty2)
    self.triangle(tx2, ty2, tx3, ty3, tx4, ty4)
  end

end



vg = OreVG.new(50)
#viewer.triangle(1, 1.1, 9.1, 2.8, 4.2, 9.4)
#viewer.triangle(11, 11.1, 19.1, 12.8, 14.2, 19.4)
vg.line(2, 5, 42, 25, 10)
Window.loop do
  Viewer.draw(vg.image, vg.triangles)
end