#!ruby -Ks # -*- mode: ruby; coding: sjis -*- # Last updated: <2016/02/14 03:42:39 +0900> # # DXRubyでスプライン曲線を描画 # 2次スプラインなのか、3次スプラインかなのか不明 # # 参考ページ # # Flashゲーム講座&ASサンプル集【曲線について】 # http://hakuhin.jp/as/curve.html#CURVE_04 require 'dxruby' require 'pp' # スプライン補間用クラス # # @attr [Array] result スプライン補間した座標群が配列の形で入る # class SplineStream # x,y座標保持用クラス class Vec attr_accessor :x attr_accessor :y def initialize(x, y) @x = x.to_f @y = y.to_f end end attr_accessor :result # コンストラクタ # @param [Array] poslist 座標群。一要素が[x,y]になってる配列 # @param [Number] interpolate 分解度 def initialize(poslist, interpolate) init(poslist, interpolate) end # 座標群から補間値を生成する # @param [Array] poslist 座標群。一要素が[x,y]になってる配列 # @param [Number] interpolate 分解度 def init(poslist, interpolate) @num = poslist.length @l = [] @aa = [] @bb = [] @cc = [] p = [] poslist.each do |d| p.push(Vec.new(d[0], d[1])) end 0.step(@num - 2, 1) do |i| p0 = p[i] p1 = p[i+1] @l[i] = Math.sqrt((p0.x - p1.x) * (p0.x - p1.x) + (p0.y - p1.y) * (p0.y - p1.y)) end @aa[0] = [0.0, 1.0, 0.5] @bb[0] = { :x => (3.0 / (2.0 * @l[0])) * (p[1].x - p[0].x), :y => (3.0 / (2.0 * @l[0])) * (p[1].y - p[0].y) } @aa[@num - 1] = [1.0, 2.0, 0.0] @bb[@num - 1] = { :x => (3.0 / @l[@num - 2]) * (p[@num - 1].x - p[@num - 2].x), :y => (3.0 / @l[@num - 2]) * (p[@num - 1].y - p[@num - 2].y) } 1.step(@num - 2, 1) do |i| a = @l[i - 1] b = @l[i] @aa[i] = [b, 2.0 * (b + a), a] @bb[i] = { :x => (3.0 * (a * a * (p[i + 1].x - p[i].x)) + 3.0 * b * b * (p[i].x - p[i - 1].x)) / (b * a), :y => (3.0 * (a * a * (p[i + 1].y - p[i].y)) + 3.0 * b * b * (p[i].y - p[i - 1].y)) / (b * a) } end 1.step(@num - 1, 1) do |i| d = @aa[i-1][1] / @aa[i][0] @aa[i] = [0, @aa[i][1] * d - @aa[i-1][2], @aa[i][2] * d] @bb[i][:x] = @bb[i][:x] * d - @bb[i - 1][:x] @bb[i][:y] = @bb[i][:y] * d - @bb[i - 1][:y] @aa[i][2] /= @aa[i][1] @bb[i][:x] /= @aa[i][1] @bb[i][:y] /= @aa[i][1] @aa[i][1] = 1 end @cc[@num - 1] = { :x => @bb[@num-1][:x], :y => @bb[@num-1][:y] } (@num - 1).step(1, -1) do |j| @cc[j - 1] = { :x => @bb[j - 1][:x] - @aa[j - 1][2] * @cc[j][:x], :y => @bb[j - 1][:y] - @aa[j - 1][2] * @cc[j][:y] } end outdata = [] count = 0 0.step(@num - 2, 1) do |i| a = @l[i] v00 = p[i].x v01 = @cc[i][:x] v02 = (p[i + 1].x - p[i].x) * 3.0 / (a * a) - (@cc[i + 1][:x] + 2 * @cc[i][:x]) / a v03 = (p[i + 1].x - p[i].x) * (-2.0 / (a * a * a)) + (@cc[i + 1][:x] + @cc[i][:x]) * (1.0 / (a * a)) v10 = p[i].y v11 = @cc[i][:y] v12 = (p[i + 1].y - p[i].y) * 3.0 / (a * a) - (@cc[i + 1][:y] + 2 * @cc[i][:y]) / a v13 = (p[i + 1].y - p[i].y) * (-2.0 / (a * a * a)) + (@cc[i + 1][:y] + @cc[i][:y]) * (1.0 / (a * a)) t = 0.0 t_spd = a.to_f / interpolate.to_f 0.step(interpolate - 1, 1) do |j| tx = ((v03 * t + v02) * t + v01) * t + v00 ty = ((v13 * t + v12) * t + v11) * t + v10 outdata.push([tx, ty]) t += t_spd end end outdata.push([p[@num-1].x, p[@num-1].y]) @result = outdata end end if $0 == __FILE__ # ---------------------------------------- # 動作確認 Window.caption = "Spline Curve" # 四角(塗り潰し)を描画 def draw_dot(x, y, sz, col) sz = sz / 2.0 Window.drawBoxFill(x - sz, y - sz, x + sz, y + sz, col) end # 点を描画 def draw_dot2(x, y, col) Window.drawLine(x, y, x, y, col) end # 四角(線のみ)を描画 def draw_box(x, y, sz, col) sz = sz / 2.0 x1, y1 = x - sz, y - sz x2, y2 = x + sz, y + sz Window.drawLine(x1, y1, x2, y1, col) Window.drawLine(x1, y2, x2, y2, col) Window.drawLine(x1, y1, x1, y2, col) Window.drawLine(x2, y1, x2, y2, col) end fnt = Font.new(12) # 1区間を何分割するか interpolate = 16 # 初期座標群 poslist = [ [50, 200], [150, 200], [250, 100], [350, 400], [450, 200], [550, 100], ] spline = SplineStream.new(poslist, interpolate) mode = 0 Window.loop do break if Input.keyPush?(K_ESCAPE) if Input.mousePush?(M_LBUTTON) # マウスボタンがクリックされたら座標変更 poslist[mode] = [Input.mousePosX.to_f, Input.mousePosY.to_f] mode += 1 mode = 0 if mode >= poslist.length # スプライン曲線を作り直し spline.init(poslist, interpolate) end # 制御点を描画 poslist.each_with_index do |d, i| x, y = d draw_dot(x, y, 6, C_RED) draw_box(x, y, 12, C_CYAN) if i == mode end # スプライン曲線を描画 spline.result.each do |d| x, y = d draw_dot2(x, y, C_WHITE) end Window.drawFont(4, 4, "Please Click", fnt) end end