SSブログ

ポリゴンレンダラ - その14 [プログラミング]

こないだからやってたポリゴン表示アプリの実装をそろそろ始めたい。そのアプリではモデリングやその編集はできない(もちろんするつもりはない。そんなの大変)ので、なんらかのできあがったモデルをファイルから読み込むことになる。最初はそのファイル形式を適当に決めようと思ってたんだけど、ちょうどいいファイル形式があることをたまたま知った。そこでじぶんかってな形式ではなく、そのすでにあるファイル形式を読み込めるようにすることにした。そのファイル形式の話。

11  ポリゴン読み込み用ファイル形式PLY

僕にとっては3Dは形状把握のためで、これまであまりフォトリアリスティックなレンダリングに興味がそれほどなかった(ある種のトゥーンレンダリングには興味があると言えるが)。そのせいで全然知らなかったんだけど、PLYというファイル形式がある。PLYファイルは記述のしかたが、ちょうどGraphicsComplexの形(頂点座標の配列とそのインデクス値の配列を組にする)になったファイル形式で、Mathematicaもversion6からImport,Exportでサポートしていた。ほんとに全然知らなかった。というか僕が知らなかっただけで、GraphicsComplexの形式は3Dモデリングの分野では当たり前らしい。

PLYはけっこう古くて
  • ポリゴンだけを格納するフォーマット
  • ASCIIフォーマットがあって、構造は簡単
  • Stanford大学発のオープンソース
  • オープンなモデルデータベースがある
  • ユーザ定義で拡張できる
というようなもので、今回のファイル形式としてはちょうどいい。

ここここにPLY形式のファイルの読み書きをするコードがあるし、サンプルにできるPLYファイルもある。

ファイルの構造はリンク先を見てもらえればわかるけど、行指向で、キーワードが並んでいるだけのもの。ただし、直交性はあまりよくなく、あるキーワードの次の行にくるキーワードが実質的に決まっていたりする。そう言う場合はひとつにまとめるとか、入れ子の表現ができるようにするとかになっていたほうが、読み込みやすい。

ファイル形式はASCIIとバイナリがあってバイナリはbig endianとlittle endianの両方をサポートしている。圧縮のためのバイナリ形式があるということからも、古いフォーマットだということがわかる(暗号化などのためでなく、単にファイルサイズを小さくするためなら、ASCIIファイルをzip圧縮するほうが小さくなる可能性が高い)。

先のサイトにある読み書き用コードは、K&RレベルのCで書かれていて非常にシンプルなものだけど、今回のアプリに使う場合、読むことだけができればいいので、これを読んで改造するよりフォーマットに従ってCocoa/Objective-Cで0から書いたほうが簡単だと思って書いてしまった。僕はあまりはやいうちからコードを書いてしまうとそのうちぐちゃぐちゃになってわからなくなってしまうことが多いので、我慢するようにしてるんだけど、考えてるうちに書いてしまっていた。

ひょっとして今回のアプリだけでなく、今後も使うことになるかもしれないので、読み取りは汎用的な結果を返すことにした。

つまりNSDictionaryとNSArrayの組み合わせで、ちょうどPorpertyListの形式と同じにした。ポリゴンの数が多いと冗長な表現になるけど、これのほうが他に使い回ししやすい。

たとえばこのcube.plyという例
ply
format ascii 1.0
comment created by platoply
element vertex 8
property float32 x
property float32 y
property float32 z
element face 6
property list uint8 int32 vertex_indices
end_header
-1 -1 -1 
1 -1 -1 
1 1 -1 
-1 1 -1 
-1 -1 1 
1 -1 1 
1 1 1 
-1 1 1 
4 0 1 2 3 
4 5 4 7 6 
4 6 2 1 5 
4 3 7 4 0 
4 7 3 2 6 
4 5 1 0 4 
は、読み取ってPropertyListのXML形式で出力すると
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>comments</key>
    <array>
        <string>created by platoply</string>
    </array>
    <key>faceHeader</key>
    <dict>
        <key>count</key>
        <integer>6</integer>
        <key>listProperty</key>
        <dict>
            <key>listElementType</key>
            <string>int32</string>
            <key>listNumberType</key>
            <string>uint8</string>
            <key>propertyName</key>
            <string>vertex_indices</string>
        </dict>
    </dict>
    <key>format</key>
    <string>ascii</string>
    <key>version</key>
    <real>1</real>
    <key>vertexHeader</key>
    <dict>
        <key>count</key>
        <integer>8</integer>
        <key>scalarProperty</key>
        <array>
            <dict>
                <key>propertyName</key>
                <string>x</string>
                <key>propertyType</key>
                <string>float32</string>
            </dict>
長いんで、途中でやめるけどこんな感じになる。これならCocoa/Objective-Cのレベルではなんとでもなる。

とりあえず、頂点に色がついていたらそれは残すことにしたけど、edge要素やユーザ定義要素は無視している。必要になれば継ぎ足すのは簡単。それと答え合わせ用にMathematicaのGraphicsComplexオブジェクトを文字列で返すメソッドも作った。これをMathematicaに読み込ませて、もとのplyファイルをImportしたのと較べればデバグになる。

じっさいにやってみたのがこれ。
0708verify.jpg

左がImportでサンプルファイルを読みこんだもの、右がさっき書いたCocoa/Objective-CのオブジェクトがGraphicsComplexを(文字列として)出力したものをMathematicaに読んで表示したもの。これだけみればちゃんとできていそう。

その、読み込み用クラスのヘッダは
@class FGStringEnumerator;
@interface FGPlyParser : NSObject {
    FGStringEnumerator  *strEnum;
    NSMutableDictionary *parsedDic;
}

- (id)initWithURL:(NSURL *)url error:(NSError **)error;
- (NSDictionary *)parseResult;
- (NSString *)mathematicaGraphicsComplexFormat;
みたいな感じ。本来なら結果を要求されたらファイルを読んで解析する、というふうな書き方をすべきなんだけど、めんどくさいのと実際にはこのクラスのインスタンスを作ったらすぐ解析結果を要求してインスタンスは捨てる、というのが普通だろうから、init...の中で全部やってしまった。良い子はマネをしないように。

また、この下請けクラスとして
@interface FGStringEnumerator : NSObject {
    NSString    *contents;
    NSArray     *lastWords;
    NSRange     enumRange;
    NSUInteger  lineNumber;
}

- (id)initWithURL:(NSURL *)url error:(NSError **)error;
- (NSString *)nextLine; //  return nil if eof
- (NSString *)wordAtIndex:(NSUInteger)index;
- (NSUInteger)numberOfWordsInCurrentLine;

- (NSUInteger)currentLineNumber;

- (NSString *)headWord;
- (NSString *)firstWord;
- (NSString *)secondWord;
@end
みたいなのを作った。これはファイルから読み込んだ文字列を行ごとに分けて、さらにその行にある単語を別々に取り出せるようにした簡単なクラス。こういうのは似たようなのを何度も書いたような気がする。

これはずっと昔からこのサイトを参考にさせてもらって書いたもの。

ASCIIファイルを読んで解析するというのはよくやることなのに、Cocoa/Objective-Cでトークンにわけるってどうやるんだっけ?ってすぐなってしまう。こういうちょっとしたTipsをすぐ見ることができて、メンテされないまでも消さずに残してくれている、というのは僕のような記憶力の乏しいプログラマにはありがたい。

今回のCocoa/Objective-Cでのplyファイルの読み込みのソースは、ポリゴンレンダラ全体ができたら公開する。
nice!(0)  コメント(0)  トラックバック(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

献立07/08献立07/09 ブログトップ

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。