ベクタグラフィックスその4
現状ではパスは1つだけでOreVGクラスで扱っているが、NanoVGはサブパスを複数持つような構造になっている。これはたぶん閉じたり閉じなかったりする複数のパスを一まとめにしてグラデーションしたりするためにそうなっているんじゃないかと思うのだが、今回はこのサブパス機能を追加してみよう。でもグラデーションはずっと先。まだ色も指定できないし。
SubPath
サブパスはそれぞれ別々に点の配列とclosedフラグを持つ。サブパスが生成されるのはMoveToコマンドのタイミングとなる。このへんはHTML5Canvasと同様である。また、線の接続が途切れるので頂点配列もサブパス単位になる。まずはSubPathクラスを作る。
class SubPath attr_accessor :points, :closed, :verts def initialize @points = [] @closed = false @verts = [] end # ぶつ切りの始点 def butt_cap_start(p0, dv, w, d) pv = p0 - dv * d dlv = dv.rperp * w @verts << pv + dlv - dv @verts << pv - dlv - dv @verts << pv + dlv @verts << pv - dlv end # ぶつ切りの終点 def butt_cap_end(p0, dv, w, d) pv = p0 + dv * d dlv = dv.rperp * w @verts << pv + dlv @verts << pv - dlv @verts << pv + dlv + dv @verts << pv - dlv + dv end # 丸い始点 def round_cap_start(p0, dv, w, ncap) dlv = dv.rperp ncap.times do |i| a = i / (ncap - 1.0) * Math::PI @verts << p0 - dlv.rotate(a) * w @verts << p0 end @verts << p0 + dlv * w @verts << p0 - dlv * w end # 丸い終点 def round_cap_end(p0, dv, w, ncap) dlv = dv.rperp @verts << p0 + dlv * w @verts << p0 - dlv * w ncap.times do |i| a = i / (ncap - 1.0) * -Math::PI @verts << p0 @verts << p0 - dlv.rotate(a) * w end end end
考えるのが面倒だったので始点終点の処理はこっちに移動した。他のいろんな処理も移動すべきな気はするが、そのうち気が向いたらやる(いい加減)。
flatten_paths
MoveToコマンドを処理するタイミングでSubPathを生成する。OreVGクラスには@subpathsインスタンス変数を追加して配列を入れておく。また、従来の@pointsを扱う場面はすべてサブパス内のpointsを使うように変更する。これはcalculate_joinsやexpand_strokeも同様となるので省略する。
private def flatten_paths @commands.each do |cmd| case cmd when CmdMoveTo @subpaths << SubPath.new # 新規サブパス追加 @subpaths.last.points << Point.new(cmd) when CmdLineTo @subpaths.last.points << Point.new(cmd) when CmdClose @subpaths.last.closed = true end end @subpaths.each do |subpath| # 最初と最後の点が同じ位置だったら最後を捨ててループしていることにする if subpath.points[0] == subpath.points[-1] subpath.points.pop subpath.closed = true end # 点の次の線の向きを計算 # ループしない場合は最後の点の情報は使われない ([subpath.points.last] + subpath.points).each_cons(2) do |p0, p1| p0.dv = (p1.v - p0.v).normalize end end end
render_stroke
render_strokeも同様にサブパス単位になるが、ここでサブパス単位にわけるとサブパスが変わる際に線が強制的に途切れるようになる。
private def render_stroke @subpaths.each do |subpath| subpath.verts.each_cons(3).with_index do |ary, i| if i.even? p1, p0, p2 = ary else p0, p1, p2 = ary end triangle(p0.x, p0.y, p1.x, p1.y, p2.x, p2.y) end end end
結果
vg = OreVG.new(50) vg.move_to(4, 4) vg.line_to(45, 8) vg.move_to(20, 15) vg.line_to(44, 35) vg.line_to(10, 40) vg.width = 5 vg.line_cap = :round vg.close_path vg.stroke Window.loop do Viewer.draw(vg.image, vg.triangles, vg.subpaths.map{|sp|sp.points}.flatten) end
このようなコードで
という感じの描画ができるようになる。現時点ではコードがややこしくなるだけでメリットは無いが、まあ、最終的にはおそらく必要になるのでいつ実装してもたいして違いは無い。
今回のコードはこちら。