そうだ、Rubyをやろう(2日目)

まあ、とにかく何か作ってみようじゃないか。
最初なので、題材としては数十行程度で書けるものが良い。

う〜ん、そうさね

私としては、Rubyをより良いPerlとして使いたいので、なんか適当なテキスト処理をやってみたい気がする。そこで思い立ったのが、前にC++でやったXFile読み込み。あれはCでやるよりも、最初からもっと読み込み易いように整形されてたら良かったなぁという思いがある。そこで、単一のXFileから、頂点座標、頂点インデックス、UV座標を別々のCSVファイルに出力するRubyスクリプトを書いてみることにした。

(XFileの構造についてはこちら参照)

#!/usr/bin/ruby
# Xファイルを読み込んで、
# ・頂点座標(.vtx)と
# ・頂点インデックス(.idx)と
# ・UV座標(.uvx)を
# それぞれ別々のファイルに出力するスクリプトです。
# 元のファイル名から.xのサフィックスを取り除き、
# 上記の括弧内のサフィックスのファイル名で出力します。
# ※
# ・単一テクスチャマッピングのみサポートします(XFileの仕様)
# ・Z座標は反転されます
# ・頂点インデックスは反対周りに補正されます

if ARGV.length == 0 then
    puts "USAGE:XFile2Csv <in-file-name>"
    exit 1
end

FloatPattern = /[-+]?(?:[0-9]+(\.[0-9]*)?|(\.[0-9]+))([eE][-+]?[0-9]+)?/
UnsignedPattern = /\d+/

# ファイルからすべての文字列を読み込む
in_file_name = ARGV[0]
base_name = File.basename in_file_name, ".x"
dir_name = File.expand_path(File.dirname(in_file_name))
text = File.open(in_file_name).read

# メッシュ情報のみ抜き出す
if /^Mesh\s*\{(.*)\}/m =~ text then
    mesh = $1.strip
else
    puts "Mesh section not found."
    exit 1
end

# 頂点座標の数とインデックス座標の数を取得する
# メッシュの最初の行を頂点数とする
# 最初の空行の次の行をインデックス数とする
end_of_vertices = false
vnum = nil
inum = nil
mesh.each_line {|line|
    line.strip!

    unless vnum then
        vnum = line.split(";")[0].to_i
        next
    end
    
    if line.empty? then
        unless end_of_vertices then
            end_of_vertices = true
        end

        next
    end

    if end_of_vertices then
        inum = line.split(";")[0].to_i
        break
    end
}

unless vnum then
    puts "Unknown number of vertices."
    exit 1
end

unless inum then
    puts "Unknown number of indices."
    exit 1
end

# 頂点座標を読み込んで出力する
vcnt = 0
mesh_tail = ""        # 頂点座標より後ろのメッシュ情報を格納する

File.open(File.join(dir_name, base_name + ".vtx"), "w") {|f|
    mesh.each_line {|line|
        if vnum > vcnt then
            line.strip!
            array = line.split(";").select {|token| FloatPattern =~ token}

            if 3 == array.length then
                # Z座標反転
                array[2] = (array[2].to_f * -1).to_s

                f.puts array.join ","
                vcnt += 1
            end
        # 全頂点を読み込んだら、次の行は空行
        elsif vnum == vcnt then
            vcnt += 1
            f.puts
            next
        # それ以降の部分は別バッファへ
        else
            mesh_tail += line
        end
    }
}

# 頂点インデックスを読み込んで出力する
icnt = 0

File.open(File.join(dir_name, base_name + ".idx"), "w") {|f|
    mesh_tail.each_line {|line|
        line.strip!
        array = line.split(/;|,/).select {|token| UnsignedPattern =~ token}

        # 三角形
        if 4 == array.length then
            # 逆周りに変換して出力
            f.puts array[1] + "," + array[3] + "," + array[2]
            icnt += 1
        # 四角形の場合は、2つの三角形に分割
        elsif 5 == array.length then
            f.puts array[1] + "," + array[4] + "," + array[2]
            f.puts array[2] + "," + array[4] + "," + array[3]
            icnt += 1
        end

        if inum == icnt then
            f.puts
            break
        end
    }
}

# テクスチャマッピング情報を抜き出す
if /^\s*MeshTextureCoords\s*\{(.*)\}/m =~ mesh_tail then
    coords = $1.strip
else
    puts "MeshTextureCoords section not found."
    exit 1
end

File.open(File.join(dir_name, base_name + ".uvx"), "w") {|f|
    coords.each_line {|line|
        line.strip!
        array = line.split(";").select {|token| FloatPattern =~ token}

        if 2 == array.length then
            f.puts array.join ","
        end
    }

    f.puts
}

とりあえず動いた。

ま、まあ・・・
最初ならこんなもんじゃろ・・・