ベクタグラフィックスその5
端の処理はあるが接続点の処理が無いので今回はこれを作る。
各種フラグ
まずPointクラスに項目を追加して各種フラグを持てるようにする。
class Point < Struct.new(:v, :dv, :dmv, :len, :left, :inner_bevel, :bevel, :corner);end
lenは線の長さでフラグではないが、その次の4つはフラグとなる。それぞれ、左回りフラグ、接続点が形状にめり込むフラグ、接続点の処理をするフラグ、接続点の処理が可能フラグ、みたいな意味合いとなる。calculate_joinsの中でcorner以外の3つを設定する。cornerはベジェ曲線を構成するときに使うので今回はflatten_pathsの中でtrue固定にしている。
private def calculate_joins(w, line_join, miter_limit) iw = w > 0 ? 1.0 / w : 0 @subpaths.each do |subpath| # 点の両側の線の中間を算出する ([subpath.points.last] + subpath.points).each_cons(2) do |p0, p1| p1.dmv = (p0.dv.rperp + p1.dv.rperp) * 0.5 dmr2 = p1.dmv.x * p1.dmv.x + p1.dmv.y * p1.dmv.y if dmr2 > 0.000001 scale = 1.0 / dmr2 if scale > 600.0 scale = 600.0 end p1.dmv *= scale end # 左回りフラグ cross = p1.dv.x * p0.dv.y - p0.dv.x * p1.dv.y if cross > 0 p1.left = true end # 交点が算出できないフラグ? limit = [1.01, [p0.len, p1.len].min * iw].max if dmr2 * limit * limit < 1.0 p1.inner_bevel = true end # join処理が必要フラグ if p1.corner if dmr2 * miter_limit * miter_limit < 1.0 or line_join == :bevel or line_join == :round p1.bevel = true end end end end end
expand_stroke
頂点を生成するところでこのフラグとOreVGのインスタンス変数@line_joinを見て丸い接続点(round)と切り捨てる接続点(bevel)の2種類を呼び分ける。ループするほうだけ抜粋。
private def expand_stroke(w, line_cap, line_join, miter_limit) ncap = curve_divs(w, Math::PI, 0.25) calculate_joins(w, line_join, miter_limit) @subpaths.each do |subpath| if subpath.closed # ループしている場合 # 頂点生成 ([subpath.points.last] + subpath.points).each_cons(2) do |p0, p1| if p1.inner_bevel or p1.bevel if line_join == :round subpath.round_join(p0, p1, w, w, ncap) else subpath.bevel_join(p0, p1, w, w) end else subpath.verts << p1.v + p1.dmv * w subpath.verts << p1.v - p1.dmv * w end end subpath.verts << subpath.verts[0] subpath.verts << subpath.verts[1]
join系メソッドの引数でwが2個あるのは無駄に見えるが、これは塗りつぶし系の処理で呼ぶときに違う値を入れて使うっぽい。
bevel_join
大きく左回りと右回りにロジックが分かれていて、bevelフラグが立っているかどうかで更に分かれる。左右のロジックの違いは符号と頂点順だけである。
# bevel join def bevel_join(p0, p1, lw, rw) dlv0 = p0.dv.rperp dlv1 = p1.dv.rperp if p1.left lv0, lv1 = choose_bevel(p1.inner_bevel, p0, p1, lw) @verts << lv0 @verts << p1.v - dlv0 * rw if p1.bevel @verts << lv0 @verts << p1.v - dlv0 * lw @verts << lv1 @verts << p1.v - dlv1 * lw else lv = p1.v - p1.dmv * rw @verts << p1.v @verts << p1.v - dlv0 * rw @verts << rv @verts << rv @verts << p1.v @verts << p1.v - dlv1 * rw end @verts << lv1 @verts << p1.v - dlv1 * rw else rv0, rv1 = choose_bevel(p1.inner_bevel, p0, p1, -rw) @verts << p1.v + dlv0 * lw @verts << rv0 if p1.bevel @verts << p1.v + dlv0 * lw @verts << rv0 @verts << p1.v + dlv1 * lw @verts << rv1 else lv = p1.v + p1.dmv * lw @verts << p1.v + dlv0 * lw @verts << p1.v @verts << lv @verts << lv @verts << p1.v + dlv1 * lw @verts << p1.v end @verts << p1.v + dlv1 * lw @verts << rv1 end end end
bevelフラグが立ってないのにbevel_joinが呼ばれるってどゆこと?って感じだが、inner_bevelが立っていると呼ばれる。line_joinが:miterの時にinner_bevelが立つとここに来るわけだ。なのでif bevelのelse側は:miter指定時の形状がめり込んでるときの特殊処理と考えればたぶん正解だろう。:bevel指定時のinner_bevelはthen側だが、この場合はchoose_bevel内で細工がされているようだ。このへんはNanoVGのコードをそのまま持ってきたような状態なのできちんと理解できているかどうか怪しい。choose_bevelはこんな感じ。
def choose_bevel(bevel, p0, p1, w) if bevel [ p1.v + p0.dv.rperp * w, p1.v + p1.dv.rperp * w ] else [ p1.v + p1.dmv * w, p1.v + p1.dmv * w ] end end
round_join
こっちは:miter指定の時に来ないのでちょっと簡単だがその代わりに回転する処理があるのでそのぶん面倒になる。
def round_join(p0, p1, lw, rw, ncap) dlv0 = p0.dv.rperp dlv1 = p1.dv.rperp if p1.left lv0, lv1 = choose_bevel(p1.inner_bevel, p0, p1, lw) a0 = Math.atan2(-dlv0.y, -dlv0.x) a1 = Math.atan2(-dlv1.y, -dlv1.x) a1 -= Math::PI*2 if a1 > a0 @verts << lv0 @verts << p1.v - dlv0 * rw n = (((a0 - a1) / Math::PI) * ncap).ceil n = 2 if n < 2 n = ncap if n > ncap n.times do |i| u = i / (n - 1.0) a = a0 + u * (a1 - a0) rx = p1.v.x + Math.cos(a) * rw ry = p1.v.y + Math.sin(a) * rw @verts << p1.v @verts << Vector.new(rx, ry) end @verts << lv1 @verts << p1.v - dlv1 * rw else rv0, rv1 = choose_bevel(p1.inner_bevel, p0, p1, -rw) a0 = Math.atan2(dlv0.y, dlv0.x) a1 = Math.atan2(dlv1.y, dlv1.x) a1 = Math::PI*2 if a1 < a0 @verts << p1.v + dlv0 * rw @verts << rv0 n = (((a1 - a0) / Math::PI) * ncap).ceil n = 2 if n < 2 n = ncap if n > ncap n.times do |i| u = i / (n - 1.0) a = a0 + u * (a1 - a0) lx = p1.v.x + Math.cos(a) * rw ly = p1.v.y + Math.sin(a) * rw @verts << Vector.new(lx, ly) @verts << p1.v end @verts << p1.v + dlv1 * rw @verts << rv1 end end