ラスタライザを作る
もう年末なのだが、ちょっと前に作ってみたラスタライザの話。といってもすごいしょぼいもので、とりあえず太さを指定した直線を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