3Dの計算

そもそも行列とはなんぞや、というところから理解できていないので、とりあえず一通り自分で実装してみた。
Direct3Dがやろうとしていることは、だいたいこういうことだろうと思う。

ソースちょっと長いけど。
カメラの設定が面倒だったからビュー変換は手抜き。

require 'dxruby'

Window.caption = "TestWindow"
Window.width = 320
Window.height = 240

image = Image.new(320,240)

class Vector
  def initialize(vec)
    @vec = vec
  end

  def *(matrix)
    result = []
    for i in 0..3
      data = 0
      for j in 0..3
        data += @vec[j] * matrix[j][i]
      end
      result.push(data)
    end
    return Vector.new(result)
  end

  def [](i)
    @vec[i]
  end
  def []=(i,d)
    @vec[i]=d
  end

end

class Matrix
  def initialize(arr)
    @arr = Array.new(4) {|i| Vector.new(arr[i])}
  end

  def *(a)
    result = []
    for i in 0..3
      result.push(@arr[i] * a)
    end
    return Matrix.new(result)
  end

  def [](i)
    @arr[i]
  end

  def self.createMatrixRotationZ(angle)
    cos = Math.cos(Math::PI/180 * angle)
    sin = Math.sin(Math::PI/180 * angle)
    return Matrix.new([
     [ cos, sin, 0, 0],
     [-sin, cos, 0, 0],
     [   0,   0, 1, 0],
     [   0,   0, 0, 1]
    ])
  end

  def self.createMatrixRotationX(angle)
    cos = Math.cos(Math::PI/180 * angle)
    sin = Math.sin(Math::PI/180 * angle)
    return Matrix.new([
     [   1,   0,   0, 0],
     [   0, cos, sin, 0],
     [   0,-sin, cos, 0],
     [   0,   0,   0, 1]
    ])
  end

  def self.createMatrixRotationY(angle)
    cos = Math.cos(Math::PI/180 * angle)
    sin = Math.sin(Math::PI/180 * angle)
    return Matrix.new([
     [ cos,   0,-sin, 0],
     [   0,   1,   0, 0],
     [ sin,   0, cos, 0],
     [   0,   0,   0, 1]
    ])
  end

  def self.createMatrixMove(x, y, z)
    return Matrix.new([
     [   1,   0,   0,   0],
     [   0,   1,   0,   0],
     [   0,   0,   1,   0],
     [   x,   y,   z,   1]
    ])
  end
end

x = 160.0
y = 120.0
z = 100.0

v = [
  Vector.new([-25.0, 25.0, 25.0,1.0]),
  Vector.new([ 25.0, 25.0, 25.0,1.0]),
  Vector.new([ 25.0, 25.0,-25.0,1.0]),
  Vector.new([-25.0, 25.0,-25.0,1.0]),
  Vector.new([-25.0,-25.0, 25.0,1.0]),
  Vector.new([ 25.0,-25.0, 25.0,1.0]),
  Vector.new([ 25.0,-25.0,-25.0,1.0]),
  Vector.new([-25.0,-25.0,-25.0,1.0]),
]

anglex = 30
angley = 0
anglez = 0

Window.loop do
  image.fill([212,208,200])

  # world
  m = Matrix.createMatrixRotationX(anglex) *
      Matrix.createMatrixRotationY(angley) *
      Matrix.createMatrixRotationZ(anglez) *
      Matrix.createMatrixMove(x, y, z) *     # local→world
      Matrix.createMatrixMove(-160, -120, 0) # world→view

  # projection
  zn = 100.0
  zf = 300.0
  sw = 160.0
  sh = 120.0
  n = 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]
        ])

  # viewport
  vp = Matrix.new([
     [ 160,   0,   0,   0],
     [   0, 120,   0,   0],
     [   0,   0,   1,   0],
     [ 160, 120,   0,   1]
    ])

  tv = []
  v.each do |a|
    a = a*m*n
    a[0] = a[0] / a[3]
    a[1] = a[1] / a[3]
    a[2] = a[2] / a[3]
    a[3] = a[3] / a[3]
    a *= vp
    tv.push(a)
  end

  image.line(tv[0][0], tv[0][1], tv[1][0], tv[1][1], [0,0,0])
  image.line(tv[1][0], tv[1][1], tv[2][0], tv[2][1], [0,0,0])
  image.line(tv[2][0], tv[2][1], tv[3][0], tv[3][1], [0,0,0])
  image.line(tv[3][0], tv[3][1], tv[0][0], tv[0][1], [0,0,0])
  image.line(tv[4][0], tv[4][1], tv[5][0], tv[5][1], [0,0,0])
  image.line(tv[5][0], tv[5][1], tv[6][0], tv[6][1], [0,0,0])
  image.line(tv[6][0], tv[6][1], tv[7][0], tv[7][1], [0,0,0])
  image.line(tv[7][0], tv[7][1], tv[4][0], tv[4][1], [0,0,0])
  image.line(tv[0][0], tv[0][1], tv[4][0], tv[4][1], [0,0,0])
  image.line(tv[1][0], tv[1][1], tv[5][0], tv[5][1], [0,0,0])
  image.line(tv[2][0], tv[2][1], tv[6][0], tv[6][1], [0,0,0])
  image.line(tv[3][0], tv[3][1], tv[7][0], tv[7][1], [0,0,0])

  Window.draw(0,0,image)
  angley = angley + 1
  anglez = anglez + 1
end