-
-
Save mirichi/5ff3c3e3cf409a338d4cb40251690c37 to your computer and use it in GitHub Desktop.
始点終点とループ処理追加
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
attr_accessor :image, :triangles, :points, :width, :line_cap | |
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 CmdMoveTo < Vector;end | |
class CmdLineTo < Vector;end | |
class CmdClose;end | |
class Point < Struct.new(:v, :dv, :dmv);end | |
def initialize(l) | |
@image = Image.new(l, l, C_WHITE) | |
@triangles = [] | |
@commands = [] | |
@points = [] | |
@verts = [] | |
@width = 1 | |
@line_cap = :butt | |
@closed = false | |
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 | |
@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 | |
# コマンドをコマンド配列に追加 | |
# このタイミングで変形などの処理も行う | |
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 | |
@points << Point.new(cmd) | |
when CmdLineTo | |
@points << Point.new(cmd) | |
when CmdClose | |
@closed = true | |
end | |
end | |
# 最初と最後の点が同じ位置だったら最後を捨ててループしていることにする | |
if @points[0] == @points[-1] | |
@points.pop | |
@closed = true | |
end | |
# 点の次の線の向きを計算 | |
# ループしない場合は最後の点の情報は使われない | |
([@points.last] + @points).each_cons(2) do |p0, p1| | |
p0.dv = (p1.v - p0.v).normalize | |
end | |
end | |
# 線の接続に必要な情報を計算する | |
private def calculate_joins | |
# 点の両側の線の中間を算出する | |
([@points.last] + @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 | |
# ぶつ切りの始点 | |
private 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 | |
# ぶつ切りの終点 | |
private 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 | |
# 丸い始点 | |
private 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 | |
# 丸い終点 | |
private 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 | |
# 弧の点の数を算出する | |
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 | |
if @closed # ループしている場合 | |
# 頂点生成 | |
(@points + [@points.first]).each do |p0| | |
@verts << p0.v + p0.dmv * w | |
@verts << p0.v - p0.dmv * w | |
end | |
else # していない場合 | |
ncap = curve_divs(w, Math::PI, 0.25) | |
# 始点の処理 | |
p0 = @points[0] | |
case @line_cap | |
when :butt | |
butt_cap_start(p0.v, p0.dv, w, -0.5) | |
when :square | |
butt_cap_start(p0.v, p0.dv, w, w - 1) | |
when :round | |
round_cap_start(p0.v, p0.dv, w, ncap) | |
end | |
# 中間の頂点生成 | |
@points[1..-2].each do |p0| | |
@verts << p0.v + p0.dmv * w | |
@verts << p0.v - p0.dmv * w | |
end | |
# 終点の処理 | |
p0 = @points[-2] | |
p1 = @points[-1] | |
case @line_cap | |
when :butt | |
butt_cap_end(p1.v, p0.dv, w, -0.5) | |
when :square | |
butt_cap_end(p1.v, p0.dv, w, w - 1) | |
when :round | |
round_cap_end(p1.v, p0.dv, w, ncap) | |
end | |
end | |
end | |
def stroke | |
flatten_paths | |
expand_stroke(@width / 2.0) | |
render_stroke | |
end | |
end | |
vg = OreVG.new(50) | |
vg.move_to(10, 38) | |
vg.line_to(15, 6) | |
vg.line_to(44, 10) | |
vg.line_to(42, 40) | |
vg.width = 10 | |
vg.line_cap = :round | |
#vg.close_path | |
vg.stroke | |
Window.loop do | |
Viewer.draw(vg.image, vg.triangles, vg.points) | |
end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment