Jewel-mmo開発日記

RubyでMMORPGを作る過程を記録する日記。 Yokohama.rb 発起人。
2004-06-17

[開発ログ]Tk3d実行速度の違い(Windows)

同じWindowsマシンでもcygwinとmswin32で速度が異なる。 tkのバージョンのせいだろうか?

mswin32

>ruby -v
ruby 1.8.1 (2003-12-25) [i386-mswin32]
ruby -rtk -e 'p Tk::TCL_VERSION'
"8.3"

loop time(1.344ms)

cygwin

$ ruby -v
ruby 1.8.1 (2003-12-25) [i386-cygwin]
$ ruby -rtk -e 'p Tk::TK_VERSION'
"8.4"

loop time(1.648ms)

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 Tk3dCanvas < TkCanvas
  attr_accessor :scale
  attr_accessor :camera
  attr_accessor :scale

  def initialize(parent=nil, keys=nil)
    super
    @scale = 1.0
  end
  def quit
    @canvas.destroy
  end
end

class Tk3dCamera
  attr_accessor :ppos
  attr_accessor :angle
  attr_accessor :length
  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 flush
    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]]

    flag = (@position_last != @position || @angle_last != @angle || @length_last != @length)
    @position_last = @position.dup
    @angle_last = @angle.dup
    @length_last = @length
    flag
  end
end

class Tk3dOt
  attr_reader :z_rate
  attr_reader :tag_to_z

  def initialize(canvas3d,z_rate=1.0)
    @canvas3d = canvas3d
    @z_rate = z_rate
    @unused = {}
    @used = []
    @tag_to_z = {}

    @prims = {}
    # 描画の基準となるウィジェット。このウィジェットの描画順序がこのOtの描画順序となる
    @base_prim = TkcPolygon.new(@canvas3d, [])
  end
  def insert_unused(z,data)
    @unused[z] = [] unless @unused[z]
    @unused[z] << data
  end
  def clear_unused
    @unused.clear
  end
  def unused_empty?
    @unused.empty?
  end
  def add_prim(key,value)
    @prims[key] = value
  end
  def get_prim(key)
    @prims[key]
  end
  def get_prim_all
    @prims
  end

  def z_to_neartag(z)
    unless a = @used[z..-1]
      return nil
    end
    return a.compact.flatten[0]
  end

  def draw(max)
    c = @canvas3d
    n = 0
    @unused.each{|z,v|
      while !v.empty?
        (tag,color,pos2d) = v.shift
        c.coords(tag, pos2d)
#        @canvas3d.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

# Tk3dModel
# * モデリングデータ(構成要素)を保持
# * ワールド内での位置と角度を保持
# * OTに描画TAGを登録
class Tk3dModel
  attr_accessor :position
  attr_accessor :angle
  attr_reader :tag_to_data
  def initialize(canvas3d, ot, data)
    @canvas3d = canvas3d
    @ot = ot
    @tag_to_data = {}
    @data = data
    @data.collect!{|lcolor,color,pos|
      prim = TkcPolygon.new(@canvas3d, []) do
        outline(lcolor)
        fill(color)
      end
      tag = prim.id.to_s
      prim.addtag(tag)
      @ot.add_prim(tag,prim)
      @tag_to_data[tag] = [color,pos]
      [tag,color,pos]
    }
    @position = [0.0,0.0,0.0]
    @angle = [0.0,0.0,0.0]
  end

  def add_ot
    camera_angle = @canvas3d.camera.angle
    camera_position = @canvas3d.camera.position.collect{|v|v*=-1}
    screen_z = @canvas3d.camera.screen_z
    cx = TkWinfo.width(@canvas3d)/2 -2
    cy = TkWinfo.height(@canvas3d)/2 -2
    scale_x = @canvas3d.scale * cx / (@canvas3d.width/2)
    scale_y = @canvas3d.scale * cy / (@canvas3d.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])
    }
  end
end


if $0 == __FILE__
  TkRoot.new.bind("KeyPress-Escape", proc{ @after.stop;TkRoot.new.destroy })

  @camera = Tk3dCamera.new(1000)
  @camera.length = 500
  @canvas3d = Tk3dCanvas.new do
      width(200)
      height(160)
      background("#888888")
      pack('fill' => 'both', 'expand' => true)
  end
  @canvas3d.camera = @camera
  @canvas3d.scale = 0.2
  @ot_bg = Tk3dOt.new(@canvas3d,0)
  @ot = Tk3dOt.new(@canvas3d,8.0)
  # Model
  @model = []
  8.times{|i|
    w = -15.0
    h = -40.0
    @model << m = Tk3dModel.new(@canvas3d,@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(@canvas3d,@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

  draw_max = n*n
  TkScale.new {
    from(1)
    to(n*n)
    set(n*n)
    orient 'horizontal'
    command { |val| draw_max = val }
    pack
  }
  #--------------------
  counter = 0
  @ct = 0
  @t = Time.now
  @after = TkAfter.new(10, -1, proc { 
    if counter%2 == 0
      @camera.angle[1] += -Math::PI/180.0*2
    end
    if @camera.flush
      @ot_bg.clear_unused
      @bg_model.add_ot
    end
    if @ot.unused_empty?
      ct = 0
      @model.each{|m|
        ct += 1
        next if @ct%1 != ct%1
        m.angle[1] += Math::PI/180.0*(rand(4)+8)
        m.add_ot
      }
      @ct += 1
    end
    @ot.draw(50)
    @ot_bg.draw(draw_max)
    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