ストロークライブラリ

呪筆で使われている、ストローク処理ライブラリを紹介します。自由にご利用ください。

使い方


  local pts = {}
  stroke_add( pts, 0, 0, 0 )
  stroke_add( pts, 60, -60, w/2 )
  stroke_add( pts, 60, -120, w )
  stroke_add( pts, 0, -180, w/2 )
  stroke_add( pts, 60, -240, w )
  stroke_add( pts, 120, -200, 0 )
  stroke_set( pts )

  bs_polygon_move( x, y )

  local r,g,b = bs_fore()
  bs_fill( r,g,b, 255 )

このように、"XY座標" と "線の幅" を指定することで、ブラシストロークのような形状を描画できるようになるライブラリです。追加された頂点列 (三点以上指定) を、滑らかに繋いだ形状に変換して(多角形近似して)、多角形スタック (bs_polygonのスタック)に追加します。

ストローク用の変数は、「local pts = {}」という形で初期化します。stroke_add( pts, X座標, Y座標, 線の直径 ) 関数で頂点を追加していき、追加が終わったら、stroke_set( pts ) で決定します。セットされた多角形スタックは、bs_polygon系の命令で操作可能です。

以下、ライブラリです。結構長いですが、stroke_add, stroke_set 命令以外は呼び出す必要はありません。

ライブラリ

---------------------------------------------------------------------------
-- begin stroke library
---------------------------------------------------------------------------

_gStrokeNum = 0

function bezier( b1, b2, b3, b4, t )
  local ti = (1.0 - t);
  local p0 = ti * ti * ti * b1
  local p1 = 3*t * ti*ti * b2
  local p2 = 3*t*t * ti * b3
  local p3 = t*t*t * b4
  return p0 + p1 + p2 + p3
end

function normalize( x, y )
  local dist = bs_distance( x, y )
  if dist == 0 then
    return x,y
  end
  return x/dist, y/dist
end

function stroke_pos( pts, idx )
  if idx < 1 then
    local vx1 = pts[1].x - pts[2].x
    local vy1 = pts[1].y - pts[2].y
    local vx2 = pts[2].x - pts[3].x
    local vy2 = pts[2].y - pts[3].y
    local a1 = bs_atan( vx1, vy1 )
    local a2 = bs_atan( vx2, vy2 )
    local adif = a1 - a2
    local x,y = bs_rotate( vx1, vy1, adif )
    return pts[1].x + x, pts[1].y + y
  end

  local s = _gStrokeNum
  if idx > s then
    local vx1 = pts[s].x - pts[s-1].x
    local vy1 = pts[s].y - pts[s-1].y
    local vx2 = pts[s-1].x - pts[s-2].x
    local vy2 = pts[s-1].y - pts[s-2].y
    local a1 = bs_atan( vx1, vy1 )
    local a2 = bs_atan( vx2, vy2 )
    local adif = a1 - a2
    local x,y = bs_rotate( vx1, vy1, adif )
    return pts[s].x + x, pts[s].y + y
  end

  return pts[idx].x, pts[idx].y
end

function stroke_width( pts, idx )
  if idx < 1 then
    return pts[1].w
  end

  local s = _gStrokeNum
  if idx > s then
    return pts[s].w
  end

  return pts[idx].w
end

function stroke_interpolate( pts, t )

  local idx = math.floor( t )
  local f = t - idx

  local px4,py4 = stroke_pos( pts, idx+2 )
  local px3,py3 = stroke_pos( pts, idx+1 )
  local px2,py2 = stroke_pos( pts, idx+0 )
  local px1,py1 = stroke_pos( pts, idx-1 )

  local vx2 = px4 - px2
  local vy2 = py4 - py2
  local vx1 = px3 - px1
  local vy1 = py3 - py1

  local len = bs_distance( px3-px2, py3-py2 )
  local m = 0.35

  vx1,vy1 = normalize( vx1, vy1 )
  vx1 = vx1 * len * m
  vy1 = vy1 * len * m

  vx2,vy2 = normalize( vx2, vy2 )
  vx2 = vx2 * len * m
  vy2 = vy2 * len * m

  local cx1 = pts[idx].x + vx1
  local cy1 = pts[idx].y + vy1
  local cx2 = pts[idx+1].x - vx2
  local cy2 = pts[idx+1].y - vy2

  local x = bezier( px2, cx1, cx2, px3, f )
  local y = bezier( py2, cy1, cy2, py3, f )

  local w1 = stroke_width( pts, idx )
  local w2 = stroke_width( pts, idx+1 )
  local w = w1 + (w2 - w1) * f

  return x,y,w
end

function stroke_normal( x_, x, y_, y, w )
  local nx = x_ - x
  local ny = y_ - y
  nx,ny = normalize( nx, ny )
  nx,ny = bs_rotate( nx, ny, 3.14159/2 )
  return nx*w/2, ny*w/2
end

function stroke_subdiv( pts, idx )
  local x1,y1,w1 = stroke_interpolate( pts, idx + 0.0 )
  local x2,y2,w2 = stroke_interpolate( pts, idx + 0.25 )
  local x3,y3,w3 = stroke_interpolate( pts, idx + 0.50 )
  local x4,y4,w4 = stroke_interpolate( pts, idx + 0.75 )
  local x5,y5,w5 = stroke_interpolate( pts, idx + 0.999 )

  local dist = 0
  dist = dist + bs_distance( x2-x1, y2-y1 )
  dist = dist +  bs_distance( x3-x2, y3-y2 )
  dist = dist +  bs_distance( x4-x3, y4-y3 )
  dist = dist +  bs_distance( x5-x4, y5-y4 )

  local subdiv = dist / 4

  if subdiv < 16 then
    subdiv = 16
  end

  if subdiv > 128 then
    subdiv = 128
  end

  return subdiv
end

function stroke_add( pts, x, y, w )
  local idx = _gStrokeNum + 1
  pts[idx] = {}
  pts[idx].x = x
  pts[idx].y = y
  pts[idx].w = w

  _gStrokeNum = _gStrokeNum + 1
end

function stroke_set( pts )

  local s = _gStrokeNum
  if s < 3 then
    return
  end

  local x,y,w = stroke_interpolate( pts, 1 )
  local x_,y_,w_ = stroke_interpolate( pts, 1 + 0.0001 )
  local nx,ny = stroke_normal( x_, x, y_, y, w )
  bs_polygon( pts[1].x + nx, pts[1].y + ny )

  -- side 1
  local i,j
  for i=1,s-1 do

    local subdiv = math.floor( stroke_subdiv( pts, i ) )

    for j=1,subdiv-1 do
      x,y,w = stroke_interpolate( pts, i + j/subdiv )
      x_,y_,w_ = stroke_interpolate( pts, i + j/subdiv + 1/1000 )
      nx,ny = stroke_normal( x_, x, y_, y, w )
      bs_polygon( x + nx, y + ny )
    end
  end

  x,y,w = stroke_interpolate( pts, s-1 + 0.999 )
  x_,y_,w_ = stroke_interpolate( pts, s-1 + 0.9991 )
  nx,ny = stroke_normal( x_, x, y_, y, w )
  bs_polygon( pts[s].x + nx, pts[s].y + ny )

  -- side 2
  bs_polygon( pts[s].x - nx, pts[s].y - ny )

  for i=1,s-1 do

    local idx = s+1 - i
    local subdiv = math.floor( stroke_subdiv( pts, idx-1 ) )

    for j=1,subdiv-1 do
      x,y,w = stroke_interpolate( pts, idx - j/subdiv )
      x_,y_,w_ = stroke_interpolate( pts, idx - j/subdiv - 1/1000 )
      nx,ny = stroke_normal( x_, x, y_, y, w )
      bs_polygon( x + nx, y + ny )
    end
  end

  x,y,w = stroke_interpolate( pts, 1 + 0.001 )
  x_,y_,w_ = stroke_interpolate( pts, 1 )
  nx,ny = stroke_normal( x_, x, y_, y, w )
  bs_polygon( pts[1].x + nx, pts[1].y + ny )

  _gStrokeNum = 0
end
---------------------------------------------------------------------------
-- end stroke library
---------------------------------------------------------------------------