ベクタグラフィックス
新年あけましておめでとうございます。今年もよろしくお願いいたします。
さて、ちょと前に作ったラスタライザを、NanoVGを参考にしながら手を入れていってベクタグラフィックスにチャレンジしよう。
基本的な流れ
NanoVGではnvgBeginFrame〜nvgBeginPath〜描画指定〜nvgStroke or nvgFill〜nvgEndFrameという呼び出しでベクタグラフィックスを描画する。
nvgBeginFrameでパラメータの初期化などをして、nvgBeginPathでパスをクリアして、nvgMoveTo、nvgLineToなどで絵を指定して、nvgStrokeかnvgFillで各種計算&バックエンドにデータ受け渡し、nvgEndFrameで実際の描画、となる。NanoVGでは本体とバックエンドが分離していて、標準のバックエンドはOpenGLを扱うが、DirectX11用のバックエンドなども作られている。
パラメータとかパスとか難しいことは後回しにして、とりあえず今の状態で線は引けているので、move_toとline_toを実装して、内部をNanoVG的な構造に作り直してみよう。
内部の構造
nvgMoveTo、nvgLineToなどの関数では内部のコマンド配列にコマンドを追加(nvg__appendCommands関数)するだけ、となっている。このコマンド列はnvgStrokeなどの中で呼ばれるnvg__flattenPaths関数で点の配列へ変換され、nvg__expandStroke関数でバックエンド用の頂点配列に変換されて、バックエンドに渡される。
このような多層構成になっているのは、それぞれの層でやるべきことがあるからである。具体的にはnvg__appendCommandsではコマンド内の座標をアフィン変換し、nvg__flattenPathsではベジェ曲線を直線の集合に展開し、nvg__expandStrokeでは線の始点・終点の処理や接続の計算をする。上位でやったほうが簡単で速くなることはなるべく上位でやる、ということだ。たぶん。
とりあえず構造の作り直し
今の線描画をそのまま残しておくなら頂点配列を生成するところは必要無いのでexpand_strokeは無くていいとして、append_commandsとflatten_pathsを追加してみよう。
initializeでコマンド配列と点配列のインスタンス変数を追加して
def initialize(l) @image = Image.new(l, l, C_WHITE) @triangles = [] @commands = [] @width = 1 @points = [] end
コマンドと点のクラスを追加して、
class MoveTo < Struct.new(:x, :y);end class LineTo < Struct.new(:x, :y);end class Point < Struct.new(:x, :y);end
append_commandsはこう
private def append_commands(cmd) @commands << cmd end
コマンドの追加はこんな感じ
def move_to(x, y) append_commands(MoveTo.new(x, y)) end def line_to(x, y) append_commands(LineTo.new(x, y)) end
点配列を生成する関数flatten_paths
private def flatten_paths @commands.each do |cmd| case cmd when MoveTo @points << Point.new(cmd.x, cmd.y) when LineTo @points << Point.new(cmd.x, cmd.y) end end end
線を生成するstrokeをこのようにすると
def stroke flatten_paths @points.each_cons(2) do |p1, p2| line(p1.x, p1.y, p2.x, p2.y) end end
このようなコードで
vg = OreVG.new(50) vg.move_to(2, 5) vg.line_to(42, 25) vg.line_to(22, 40) vg.width = 10 vg.stroke Window.loop do Viewer.draw(vg.image, vg.triangles) end
思ったこと
NanoVGと同様に層を作っただけで中身はほとんどカラッポである。MoveToとLineToとPointのクラスが全部同じとかふざけてんのかって感じだが、今は何もしていないからそうなのであって、今後違いが出てくる。はず。
初めは線を連続で描画できるようにするつもりは無かったのだが、each_consで処理しとけばいいんじゃね?って思ってやってみたら繋がるようになった。
今回のコード全体はこちら。