Skip to content

Instantly share code, notes, and snippets.

@mirichi

mirichi/orevg.rb Secret

Created Jan 1, 2017
Embed
What would you like to do?
接続点の処理追加
require 'dxruby'
# ラスタライズ結果を見やすく描画する
module Viewer
def self.draw(image, triangles, points)
s = 400.0 / image.width
Window.mag_filter = TEXF_POINT
Window.draw_ex(50, 50, image, scale_x:s, scale_y:s, center_x:0, center_y:0)
image.width.times do |c|
Window.draw_line(c * s + 50, 50, c * s + 50, image.width * s + 50, [100, 100, 100])
Window.draw_line(50, c * s + 50, image.width * s + 50, c * s + 50, [100, 100, 100])
end
triangles.each do |ary|
x1, y1, x2, y2, x3, y3 = *ary
Window.draw_line(x1 * s + 50, y1 * s + 50, x2 * s + 50, y2 * s + 50, C_RED)
Window.draw_line(x2 * s + 50, y2 * s + 50, x3 * s + 50, y3 * s + 50, C_RED)
Window.draw_line(x3 * s + 50, y3 * s + 50, x1 * s + 50, y1 * s + 50, C_RED)
end
points.each do |p|
Window.draw_box_fill(p.v.x * s - 2 + 50, p.v.y * s - 2 + 50, p.v.x * s + 2 + 50, p.v.y * s + 2 + 50, C_GREEN)
end
end
end
class OreVG
class Vector < Array
def x;self[0];end
def y;self[1];end
def x=(v);self[0]=v;end
def y=(v);self[1]=v;end
def initialize(x, y)
self[0] = x
self[1] = y
end
def +(v)
Vector.new(self[0] + v.x, self[1] + v.y)
end
def -(v)
Vector.new(self[0] - v.x, self[1] - v.y)
end
def *(v)
Vector.new(self[0] * v, self[1] * v)
end
def /(v)
Vector.new(self[0] / v, self[1] / v)
end
def normalize
dx = self[0]
dy = self[1]
len = Math.sqrt(dx * dx + dy * dy)
if len > 1e-6
ilen = 1.0 / len
dx *= ilen
dy *= ilen
end
Vector.new(dx, dy)
end
def len
dx = self[0]
dy = self[1]
Math.sqrt(dx * dx + dy * dy)
end
def rperp
Vector.new(self[1], -self[0])
end
def rotate(a)
Vector.new(self[0] * Math.cos(a) - self[1] * Math.sin(a), self[0] * Math.sin(a) + self[1] * Math.cos(a))
end
def ==(v)
dx = self[0] - v.x
dy = self[1] - v.y
dx*dx + dy*dy < 0.01*0.01
end
end
class SubPath
attr_accessor :points, :closed, :verts, :debug_points
def initialize
@points = []
@closed = false
@verts = []
@debug_points = []
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
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
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
# 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
class CmdMoveTo < Vector;end
class CmdLineTo < Vector;end
class CmdClose;end
class Point < Struct.new(:v, :dv, :dmv, :len, :left, :inner_bevel, :bevel, :corner);end
attr_accessor :image, :triangles, :subpaths, :stroke_width, :line_cap, :line_join
def initialize(l)
@image = Image.new(l, l, C_WHITE)
@triangles = []
@commands = []
@stroke_width = 1.0
@line_cap = :butt
@line_join = :miter
@miter_limit = 10.0
@subpaths = []
end
# EdgeFunction
# E(x, y) = (x - X) * dy - (y - Y) * dx
# E(x, y) <= 0で中と判定。3つのエッジの内側であれば塗る。
private def rasterize(x1, y1, x2, y2, x3, y3)
dx12 = x2 - x1
dy12 = y2 - y1
dx23 = x3 - x2
dy23 = y3 - y2
dx31 = x1 - x3
dy31 = y1 - y3
# 基点。ピクセルの中心で計算する。
e1 = (0.5 - x1) * dy12 - (0.5 - y1) * dx12
e2 = (0.5 - x2) * dy23 - (0.5 - y2) * dx23
e3 = (0.5 - x3) * dy31 - (0.5 - y3) * dx31
@image.height.times do |y|
et1 = e1
et2 = e2
et3 = e3
@image.width.times do |x|
yield x, y if et1 <= 0 and et2 <= 0 and et3 <= 0
et1 += dy12
et2 += dy23
et3 += dy31
end
e1 -= dx12
e2 -= dx23
e3 -= dx31
end
end
private def triangle(x1, y1, x2, y2, x3, y3)
rasterize(x1, y1, x2, y2, x3, y3) do |x, y|
@image[x,y] = [100, 100, 255]
end
@triangles << [x1, y1, x2, y2, x3, y3]
end
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
# コマンドをコマンド配列に追加
# このタイミングで変形などの処理も行う
private def append_commands(cmd)
@commands << cmd
end
def move_to(x, y)
append_commands(CmdMoveTo.new(x, y))
self
end
def line_to(x, y)
append_commands(CmdLineTo.new(x, y))
self
end
def close_path
append_commands(CmdClose.new)
self
end
# コマンド配列から点配列を作成
# ベジェ曲線の展開はこのタイミング
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 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
p0.len = (p1.v - p0.v).len
end
end
end
# 線の接続に必要な情報を計算する
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
# 弧の点の数を算出する
private def curve_divs(r, arc, tol)
da = Math.acos(r / (r + tol)) * 2
[2, (arc / da).ceil].max
end
# 頂点配列作成
# capやjoinの処理はここで
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]
else # していない場合
# 始点の処理
p0 = subpath.points[0]
case line_cap
when :butt
subpath.butt_cap_start(p0.v, p0.dv, w, -0.5)
when :square
subpath.butt_cap_start(p0.v, p0.dv, w, w - 1)
when :round
subpath.round_cap_start(p0.v, p0.dv, w, ncap)
end
# 中間の頂点生成
subpath.points[0..-2].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
# 終点の処理
p0 = subpath.points[-2]
p1 = subpath.points[-1]
case line_cap
when :butt
subpath.butt_cap_end(p1.v, p0.dv, w, -0.5)
when :square
subpath.butt_cap_end(p1.v, p0.dv, w, w - 1)
when :round
subpath.round_cap_end(p1.v, p0.dv, w, ncap)
end
end
end
end
def stroke
flatten_paths
expand_stroke(@stroke_width / 2.0, @line_cap, @line_join, @miter_limit)
render_stroke
end
end
vg = OreVG.new(50)
vg.move_to(10, 10)
vg.line_to(33, 16)
vg.line_to(13, 38)
vg.line_to(43, 42)
vg.stroke_width = 15
vg.line_cap = :round
vg.line_join = :bevel
#vg.close_path
vg.stroke
Window.loop do
Viewer.draw(vg.image, vg.triangles, vg.subpaths.map{|sp|sp.points}.flatten + vg.subpaths.map{|sp|sp.debug_points}.flatten)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment