2004-06-11
[Ruby][開発ログ]tk3d.rbプロトタイプ
- Zクリップ (実装)
- ポリゴン単位のZソート (実装)
- 平行光源の光源計算 (未実装)
少し変わった作りをしている。部分的な描画更新が可能。具体的に言うとオーダリングテーブル単位で更新のレートを変更できる。この機能を実現するために表示済みのポリゴンの中からZ値を元に描画するポリゴンのエントリ位置を割り出す処理(z_to_neartag(z)メソッド)が入っているが、この部分が重い。サンプル実行時に下部にあるスケールバーを下げるとBGの更新間隔が変化するのがわかる。
この機能をつけた理由は、BGを初めに描画して視点変更が発生するまでキャラクターオブジェクトだけを更新する、という仕組みを実現するため。
Ruby/Tk実行環境をお持ちの方はスコアをご報告頂けるとありがたい。
手元の環境でのスコア(バーは初期値の64)
loop time(1.359ms) P4 2.8Gz WindowsXP
tk3d.rb
require("tk") module Tk3d # poss : [[x1,y1,z1],[x2,y2,z2],[x3,y3,z3],...] # angle : [ax,ay,az] def Tk3d.rotate_zyx(poss,angle) ax = angle[0] ay = angle[1] az = angle[2] poss.collect!{|px,py,pz| # Z軸回転 xx = px * Math.cos(az) - py * Math.sin(az) yy = px * Math.sin(az) + py * Math.cos(az) px = xx py = yy # Y軸回転 zz = pz * Math.cos(ay) - px * Math.sin(ay) xx = pz * Math.sin(ay) + px * Math.cos(ay) pz = zz px = xx # X軸回転 yy = py * Math.cos(ax) - pz * Math.sin(ax) zz = py * Math.sin(ax) + pz * Math.cos(ax) py = yy pz = zz [px,py,pz] } end def Tk3d.rotate_xyz(poss,angle) ax = angle[0] ay = angle[1] az = angle[2] poss.collect!{|px,py,pz| # X軸回転 yy = py * Math.cos(ax) - pz * Math.sin(ax) zz = py * Math.sin(ax) + pz * Math.cos(ax) py = yy pz = zz # Y軸回転 zz = pz * Math.cos(ay) - px * Math.sin(ay) xx = pz * Math.sin(ay) + px * Math.cos(ay) pz = zz px = xx # Z軸回転 xx = px * Math.cos(az) - py * Math.sin(az) yy = px * Math.sin(az) + py * Math.cos(az) px = xx py = yy [px,py,pz] } end def Tk3d.transfer(poss,position) mx = position[0] my = position[1] mz = position[2] poss.collect!{|pos| [pos[0]+mx,pos[1]+my,pos[2]+mz] } end def Tk3d.to_screen(poss,cx,cy,screen_z,scale_x=1.0,scale_y=1.0) npos = [] poss.each{|px,py,pz| npos << [scale_x*px*screen_z/pz + cx, scale_y*py*screen_z/pz + cy] } npos end def Tk3d.av_z(poss) av = 0 poss.each{|px,py,pz| av += pz } av /= poss.size end def Tk3d.min_z(poss) min = poss[0][2] poss.each{|px,py,pz| min = pz if pz<min } min end end class Tk3dOt attr_reader :z_rate def initialize(world,z_rate=1.0) @world = world @z_rate = z_rate @unused = {} @used = [] @tag_to_z = {} @prims = {} # 描画の基準となるウィジェット。このウィジェットの描画順序がこのOtの描画順序となる @base_prim = TkcPolygon.new(@world.canvas, []) end def insert_unused(z,data) @unused[z] = [] unless @unused[z] @unused[z] << data end def add_prim(key,value) @prims[key] = value end def get_prim(key) @prims[key] end def empty? @unused.empty? end def z_to_neartag(z) unless a = @used[z..-1] return nil end return a.compact.flatten[0] end def draw(max) c = @world.canvas n = 0 @unused.each{|z,v| while !v.empty? (tag,color,pos2d) = v.shift c.coords(tag, pos2d) # @world.get_prim(tag).fill("#000000") if old_z = @tag_to_z[tag] if @used[old_z].size <= 1 @used[old_z] = nil else @used[old_z].delete(tag) end end if ltag = z_to_neartag(z) prim = @prims[ltag] else prim = @base_prim end @prims[tag].raise(prim) @tag_to_z[tag] = z @used[z] = [] unless @used[z] @used[z] << tag return if (n += 1) >= max end @unused.delete(z) } end end class Tk3dCamera attr_reader :ppos attr_reader :angle attr_accessor :position attr_accessor :screen_z def initialize(screen_z=1000.0,ppos=nil,ang=nil) @screen_z = screen_z @ppos = ppos ? ppos : [0.0,0.0,0.0] @angle = ang ? ang : [0.0,0.0,0.0] end def length=(l) @length = l flush_position end def angle=(a) @angle = a flush_position end def ppos=(a) @ppos = a flush_position end def flush_position npos=[[0,0,-@length]] Tk3d::rotate_xyz(npos,@angle.collect{|v|v*=-1}) npos.flatten! @position = [@ppos[0]+npos[0], @ppos[1]+npos[1], @ppos[2]+npos[2]] end end class Tk3dWorld attr_accessor :scale def initialize(camera, cwidth, cheight, color) @camera = camera @canvas = TkCanvas.new do width(cwidth) height(cheight) background(color) pack('fill' => 'both', 'expand' => true) end @scale = 1.0 end def quit @canvas.destroy end def canvas @canvas end def camera @camera end end # Tk3dModel # * モデリングデータ(構成要素)を保持 # * ワールド内での位置と角度を保持 # * OTに描画TAGを登録 class Tk3dModel attr_accessor :position attr_accessor :angle def initialize(world,ot, data) @world = world @ot = ot @data = data @data.collect!{|color,pos| prim = TkcPolygon.new(@world.canvas, []) do outline("#666666") fill(color) end tag = prim.id.to_s prim.addtag(tag) @ot.add_prim(tag,prim) [tag,color,pos] } @position = [0.0,0.0,0.0] @angle = [0.0,0.0,0.0] end def draw ct = 0 camera_angle = @world.camera.angle camera_position = @world.camera.position.collect{|v|v*=-1} screen_z = @world.camera.screen_z cx = TkWinfo.width(@world.canvas)/2 -2 cy = TkWinfo.height(@world.canvas)/2 -2 scale_x = @world.scale * cx / (@world.canvas.width/2) scale_y = @world.scale * cy / (@world.canvas.height/2) near_z = 0 z_rate = @ot.z_rate @data.each{|tag,color,pos| npos = pos.dup #--ローカル変換 Tk3d::rotate_zyx(npos, @angle) Tk3d::transfer(npos, @position) #--ワールド変換 Tk3d::transfer(npos, camera_position) Tk3d::rotate_zyx(npos, camera_angle) #--スクリーン投影 if Tk3d::min_z(npos) > near_z#--nearクリップ pos2d = Tk3d::to_screen(npos, cx, cy, screen_z, scale_x, scale_y) z = (Tk3d::av_z(npos)*z_rate).to_i else pos2d = [0,0,0,0] z = (near_z*z_rate).to_i end @ot.insert_unused(z,[tag,color,pos2d]) ct += 1 } end end if $0 == __FILE__ TkRoot.new.geometry("+100+100") TkRoot.new.bind("KeyPress-Escape", proc{ @after.stop;TkRoot.new.destroy }) @camera = Tk3dCamera.new(1000) @camera.length = 500 @world = Tk3dWorld.new(@camera,200,160,"#bbbbbb") @world.scale = 0.2 @ot_bg = Tk3dOt.new(@world,0) @ot = Tk3dOt.new(@world,8.0) # Model @model = [] 8.times{|i| w = -15.0 h = -40.0 @model << m = Tk3dModel.new(@world,@ot, [ ["#777777",[[0.0,0.0,0.0],[-w,h,-w],[-w,h, w]]], ["#888888",[[0.0,0.0,0.0],[-w,h, w],[ w,h, w]]], ["#888888",[[0.0,0.0,0.0],[ w,h, w],[ w,h,-w]]], ["#777777",[[0.0,0.0,0.0],[ w,h,-w],[-w,h,-w]]], ["#999999",[[-w,h,-w],[-w,h,w],[w,h,w],[w,h,-w]]], ] ) x = i%2==0 ? i/2*40 : -(i+1)/2*40 m.position[0] = x } # BG Model n = 8 w = 64 data = [] n.times{|z| n.times{|x| h = 0.0 x1 = x4 = x*w - n*w/2 x2 = x3 = (x+0.8)*w - n*w/2 z1 = z2 = z*w - n*w/2 z3 = z4 = (z+0.8)*w - n*w/2 c = 0xAA - ((x1*x1+z1*z1)/(n*w*4)).to_i.abs c = 0x00 if c < 0x00 color = sprintf("#%02x%02x%02x",c,c,c) data << [color,[[x1,h,z1],[x2,h,z2],[x3,h,z3],[x4,h,z4]]] } } @bg_model = Tk3dModel.new(@world,@ot_bg,data) @camera.angle = [0,0,0] @camera.ppos = [0,-20,0] @camera.angle[0] = Math::PI/180.0*10 @camera.angle[1] = Math::PI/180.0*80 @camera.flush_position flush_num = n*n TkScale.new { from(1) to(n*n) set(n*n) orient 'horizontal' command { |val| flush_num = val } pack } #-------------------- counter = 0 @ct = 0 @t = Time.now @after = TkAfter.new(10, -1, proc { @camera.angle[1] += -Math::PI/180.0*1 @camera.flush_position if @ot_bg.empty? @bg_model.draw end if @ot.empty? ct = 0 @model.each{|m| ct += 1 next if @ct%1 != ct%1 m.draw m.angle[1] += Math::PI/180.0*(rand(4)+8) } @ct += 1 end @ot.draw(50) @ot_bg.draw(flush_num) counter += 1 if counter%20 == 0 puts " loop time(#{Time.now-@t}ms)" @t = Time.now end }) @after.cancel_on_exception = false @after.start #-------------------- Tk.mainloop end