擬似3D再び

手元のDXRuby1.5.0devにMatrix/VectorとSpriteの追加機能を実装してみたので、昔やってた擬似3Dの計算をするSpriteを作ってみた。

require 'dxruby'

class Sprite3D < Sprite
  attr_accessor :v

  def self.set_transform(sw, sh, zn, zf)
    @@view = Matrix.new([[1, 0, 0, 0],
                         [0, 1, 0, 0],
                         [0, 0, 1, 0],
                         [-sw/2, -sh/2, zn, 1]])
    @@proj = Matrix.new([[2.0 * zn / sw, 0,                    0, 0],
                         [0, 2.0 * zn / sh,                    0, 0],
                         [0,             0,       zf / (zf - zn), 1],
                         [0,             0, -zn * zf / (zf - zn), 0]])
    @@vp = Matrix.new([[ sw / 2,      0,   0,   0],
                       [      0, sh / 2,   0,   0],
                       [      0,      0,   -1,   0],
                       [ sw / 2, sh / 2,   0,   1]])
    @@zn = zn
  end

  def initialize(v, image)
    @v = v
    self.image = image
    self.offset_x = nil
    self.offset_y = nil
  end

  def update
    temp = Vector.new(@v.x, @v.y, @v.z, 1) * @@view * @@proj
    self.scale_x = self.scale_y = @@zn / temp.w
    self.xyz = temp / temp.w * @@vp
  end
end

Sprite3D.set_transform(640, 480, -300, -5000)
objects = Array.new(10) {
  Sprite3D.new(Vector.new(0, 0, 0), Image.new(200, 200, [rand()*255, rand()*255, rand()*255]))
}

y_angle = 0

Window.loop do
  y_angle += 2

  objects.each_with_index do |s, i|
    s.v = Vector.new(800, 0, 0) * Matrix.rotation_y(i * 36 + y_angle) * Matrix.rotation_x(-10) + Vector.new(320, 240, -1000)
  end

  Sprite.update(objects)
  Sprite.draw(objects)

  break if Input.key_push?(K_ESCAPE)
end

座標は1点の指定になるから画像の中心点と定義して、xyzのVectorをv=で設定してやると3Dのパースを計算して描画する。パースについてはSprite3D.set_transformであらかじめ設定しておく。zが0の時に等倍になるようにview行列に細工がしてある。zのソートと同様に、奥にいくほどマイナスの値になる。
空間内を移動するような擬似3Dゲーを作るにはカメラの移動とかをサポートしないといけないのでこのままではダメだが、なんとなく雰囲気はつかめる。