RayCastによる擬似3D描画

わざわざDXRubyでやるような代物ではないのだが。速度的にも、表現力的にも。

RayCastというのは古い技法で、Quakeなどのゲームで初期の頃に使われていた。
壁は2Dマップで表現し、画面を例えば640*480なら横640列と考え、視点の位置から横ピクセル単位に視線を飛ばし、2Dマップと衝突判定して、当たった距離と物に応じたテクスチャを縦一列に描画する。
今ではCPUでそんな処理するよりもポリゴンを使ってGPUに計算させたほうが速い。
RayCastでは計算の特性上、横に長い壁を正面から見たときに、中央が上下に大きく歪んで見える。もともと人間の目にもそのように見えているはずなのだが、脳がまっすぐな壁として補間しているため歪んで見えないだけだ。画像としてはポリゴンよりRayCastのほうが正確だが、脳の補正込みで画像を作ってるという意味ではポリゴンのほうが優秀だ。

#!ruby -Ks
require 'dxruby'
require 'dxrubymatrix'

line1 = [568, 438, 568, 438, 607, 261, 607, 261, 520, 200, 520, 200, 531, 151,
 531, 151, 602, 115, 602, 115, 562, 19, 562, 19, 113, 21, 113, 21, 34, 83,
 34, 83, 81, 180, 81, 180, 221, 207, 221, 207, 251, 274, 251, 274, 204, 299,
 204, 299, 61, 262, 61, 262, 26, 358, 26, 358, 53, 430]
line2 = [88, 380, 88, 380, 479, 392, 479, 392, 536, 302, 536, 302, 534, 267,
 534, 267, 461, 210, 461, 210, 459, 151, 459, 151, 518, 91, 518, 91, 487, 53,
 487, 53, 156, 77, 156, 77, 125, 110, 125, 110, 292, 197, 292, 197, 311, 268,
 311, 268, 227, 342, 227, 342, 93, 330]

$walls = []

tx = 53
ty = 430
line1.each_slice(2) do |x, y|
  s = Sprite.new(0,0)
  s.collision = [tx, ty, x, y, x, y]
  $walls << s
  tx = x
  ty = y
end
tx = 93
ty = 330
line2.each_slice(2) do |x, y|
  s = Sprite.new(0,0)
  s.collision = [tx, ty, x, y, x, y]
  $walls << s
  tx = x
  ty = y
end

$ray = []
(0..639).each do |x|
  s = Sprite.new(0,0)
  temp = Vector.new(400,0).rotate((x / 639.0 * 2 - 1) * 30)
  s.collision = [0, 0, temp.x, temp.y, temp.x, temp.y]
  s.center_x = s.center_y = 0
  $ray << s
end


x = 300 
y = 400
angle = 0

image = Image.new(1,1,C_RED)

Window.loop do

  angle += Input.x*5
  if Input.key_down?(K_UP)
    x += Math.cos(Math::PI / 180 * angle) * 3
    y += Math.sin(Math::PI / 180 * angle) * 3
  end

  $ray.each_with_index do |r, i|
    r.x = x
    r.y = y
    r.angle = angle
    ary = r.check($walls)
    z = 250000
    ax, ay = Vector.new(r.collision[0], r.collision[1]).rotate(angle).to_a
    ax += r.x
    ay += r.y
    bx, by = Vector.new(r.collision[2], r.collision[3]).rotate(angle).to_a
    bx += r.x
    by += r.y
    px = 0
    py = 0
    if ary.size > 0 then
      ary.each do |w|
        cx = w.collision[0] + w.x
        cy = w.collision[1] + w.y
        dx = w.collision[2] + w.x
        dy = w.collision[3] + w.y
        bunbo = (bx - ax) * (dy - cy) - (by - ay) * (dx - cx)

        dR = ((dy - cy) * (cx - ax) - (dx - cx) * (cy - ay) ) / bunbo

        ix = ax + dR * (bx - ax)
        iy = ay + dR * (by - ay)

        length = (ix - r.x)**2 + (iy - r.y)**2

        if length < z
          z = length
          px = ix
          py = iy
        end
      end
      z = Math::sqrt(z)
      c = 255 - z / 400 * 255
      Window.draw_line(i, 240 - 400.0 / z * 10, i, 240 + 400.0 / z * 10, [c, c, c])
      Window.draw(px, py, image)
    end
  end
  break if Input.key_push?(K_ESCAPE)
end