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 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
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
class CmdMoveTo < Vector;end
class CmdLineTo < Vector;end
class CmdClose;end
class Point < Struct.new(:v, :dv, :dmv);end
attr_accessor :image, :triangles, :subpaths, :width, :line_cap
def initialize(l)
@image = Image.new(l, l, C_WHITE)
@triangles = []
@commands = []
@width = 1
@line_cap = :butt
@closed = false
@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 # 新規サブパス追加
@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
# 線の接続に必要な情報を計算する
private def calculate_joins
@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
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)
calculate_joins
@subpaths.each do |subpath|
if subpath.closed # ループしている場合
# 頂点生成
(subpath.points + [subpath.points.first]).each do |p0|
subpath.verts << p0.v + p0.dmv * w
subpath.verts << p0.v - p0.dmv * w
end
else # していない場合
ncap = curve_divs(w, Math::PI, 0.25)
# 始点の処理
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[1..-2].each do |p0|
subpath.verts << p0.v + p0.dmv * w
subpath.verts << p0.v - p0.dmv * w
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(@width / 2.0)
render_stroke
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment