ベクタグラフィックスその6
今回は3次ベジェ曲線を作る。ベジェ曲線というのは始点終点であるアンカー2つと制御点を使って曲線を描画するアルゴリズムである。具体的にはベジェ曲線 - Wikipediaの下のほうの作図法を見てもらうとわかりやすい。また、3次ベジェ曲線体験ツールを作ったのでこれを動かしてもらえると実際どのように描画されるのかが目で見てわかる。アンカー、ハンドル、スライダーをドラッグようにしてあるのでどうすればどうなるかが理解しやすい。ちょっと小さくて使いにくいけど。
あと、ベジエ曲線について - s.h’s pageを見てもらえばいろいろと具体的なことがわかると思う。
tesselate_bezier
ベジェ曲線は任意の位置で2つのベジェ曲線に分割することができるので、NanoVGではこの特性を利用して再帰で二等分していって、直線で表現できるレベルまで細かくする。後は分割された点を配列に格納して直線として描画するだけである。この分割する関数がtesselate_bezierで、このようになる。
def tesselate_bezier(x1, y1, x2, y2, x3, y3, x4, y4, level, type) return if level > 10 dx = x4 - x1 dy = y4 - y1 d2 = ((x2 - x4) * dy - (y2 - y4) * dx).abs d3 = ((x3 - x4) * dy - (y3 - y4) * dx).abs if (d2 + d3) * (d2 + d3) < 0.25 * (dx * dx + dy * dy) point = Point.new(Vector.new(x4, y4)) point.corner = type @points << point return end x12 = (x1 + x2) * 0.5 y12 = (y1 + y2) * 0.5 x23 = (x2 + x3) * 0.5 y23 = (y2 + y3) * 0.5 x34 = (x3 + x4) * 0.5 y34 = (y3 + y4) * 0.5 x123 = (x12 + x23) * 0.5 y123 = (y12 + y23) * 0.5 x234 = (x23 + x34) * 0.5 y234 = (y23 + y34) * 0.5 x1234 = (x123 + x234) * 0.5 y1234 = (y123 + y234) * 0.5 tesselate_bezier(x1, y1, x12, y12, x123, y123, x1234, y1234, level + 1, false) tesselate_bezier(x1234, y1234, x234, y234, x34, y34, x4, y4, level + 1, type) end end
大きな曲線を描画するとものすごく細かくなってしまうので、とりあえず10回で再帰を諦めるようになっている。最後の引数はcornerフラグに使われる。ベジェ曲線の途中では線の接続処理はしないという意味になる。このメソッドはSubPathクラスに追加した。ちなみに3次ベジェ曲線体験ツールの描画もこのメソッドを使っている。
他
ベジェ曲線描画用のコマンドクラスは
class CmdBezierTo < Struct.new(:h1x, :h1y, :h2x, :h2y, :x, :y);end
となる。ユーザが呼ぶメソッドは
def bezier_to(h1x, h1y, h2x, h2y, x, y) append_commands(CmdBezierTo.new(h1x, h1y, h2x, h2y, x, y)) self end
で、flatten_pathsは
private def flatten_paths @commands.each do |cmd| case cmd when CmdMoveTo @subpaths << SubPath.new # 新規サブパス追加 point = Point.new(cmd) point.corner = true @subpaths.last.points << point when CmdLineTo point = Point.new(cmd) point.corner = true @subpaths.last.points << point when CmdBezierTo last = @subpaths.last.points.last.v @subpaths.last.tesselate_bezier(last.x, last.y, cmd.h1x, cmd.h1y, cmd.h2x, cmd.h2y, cmd.x, cmd.y, 0, true) when CmdClose @subpaths.last.closed = true end end
結果
ベジェ曲線を描画できるようになった。
また、
def ellipse(cx, cy, rx, ry) append_commands(CmdMoveTo.new(cx-rx, cy)) append_commands(CmdBezierTo.new(cx-rx, cy+ry*KAPPA90, cx-rx*KAPPA90, cy+ry, cx, cy+ry)) append_commands(CmdBezierTo.new(cx+rx*KAPPA90, cy+ry, cx+rx, cy+ry*KAPPA90, cx+rx, cy)) append_commands(CmdBezierTo.new(cx+rx, cy-ry*KAPPA90, cx+rx*KAPPA90, cy-ry, cx, cy-ry)) append_commands(CmdBezierTo.new(cx-rx*KAPPA90, cy-ry, cx-rx, cy-ry*KAPPA90, cx-rx, cy)) append_commands(CmdClose.new) self end
このようなメソッドで楕円を描画できるようにもなる。KAPPA90は
KAPPA90 = 0.5522847493