mqoローダを作ってみる

ソース
http://github.com/ousttrue/irrmqo


pmdローダから始めようとしたら頓挫したので、敷居を下げて動かないモデルのローダからやってみることにした。ここのところpythonをメインで使っていたため、C++に対応できなくて妙に苦労したが何とか完了。
Irrlichtはチュートリアルに書いてあることそのままならさくっとできるが、違うことをやるにはリファレンス見ながら必要な場合はソースを読む必要がある感じ。幸いなことにIrrlichtの本体はわりとコンパクトなのでソースを読むことはそんなに苦ではない。逆にドキュメントだけから情報を得ようとすると苦しい。
今回参考にしたのは、本家のチュートリアルと日本語のチュートリアル2つ
http://irrlicht.sourceforge.net/tutorials.html
http://www.zgock-lab.net/irrlicht/
http://lesson.ifdef.jp/lesson.html
こちらのサイト
http://sssiii.seesaa.net/


Irrlichtを使うにはまず、本家や上記のチュートリアルをビルドして適当に眺める。
次に、やりたいことに近いコードをチュートリアルやネットから探してhttp://irrlicht.sourceforge.net/docu/hierarchy.htmlを見ながら改造し、インターフェースの構成を理解する。
構成に慣れると、本体ソースのファイル名からやりたいことを書いてあるファイルが類推できるようになるので、本体のコードを参考にしながら進めるという感じになった。

ファイルローダを作るには?

参考
http://sssiii.seesaa.net/article/103944544.html

irr::scene::IMeshLoader
を継承したクラスを作ってやればいい。

IMeshLoaderの継承するメンバ関数

virtual IAnimatedMesh * createMesh (io::IReadFile *file)=0
virtual bool isALoadableFileExtension (const io::path &filename) const =0

IAnimatedMeshはどういったインターフェースなのかというと

IMesh // 動かないメッシュ
↑
IAnimatedMesh // 動くメッシュ
↑
ISkinnedMesh // ボーンで動くメッシュ

というような継承関係を持つ。
今回は動かないメッシュを作るのでIMeshインターフェースでよさそうなものだが、createMeshはIAnimatedMeshを要求している。そこで同様に動かないobjの本体ソースCOBJMeshFileLoaderを参照したところSAnimatedMeshを作って返していた。SAnimatedMeshは中にIMeshの実装であるSMeshを保持する単なる入れ物の様子。他のCMD2MeshFileLoaderなどでは、自前でIAnimatedMeshを継承してCAnimatedMeshMD2などを作っていた(quakeのmd2フォーマット調べてみたが、これはボーンじゃなくてモーフィング変形していると見受けた)。今回作ったmqoローダでは、SMeshにデータを入れてSAnimatedMeshに格納したものをcreateMeshで生成した。
ついでにISkinnedMeshについて書いておくと、ボーンアニメーション向けのインタフェースのようだ。CXMeshFileLoaderなどがこれを生成していた。次以降はこっちのISkinnedMeshを使う必要がある。

IMesh ←------------------SMesh(実装)
↑                                    ↑保持
IAnimatedMesh←----- SAnimatedMesh(実装)
↑
ISkinnedMesh←-------- CSkinnedMesh(実装。ソース内にあり。CXMeshFileLoaderが作る)

Irrlicht内のクラスの命名規則に関して

先頭のプリフィックス、"I"はインターフェースはわりとあきらか。
"S"はStruct、"C"はClassかと思ったが微妙に違う。
例外はあるが、今のところ"S"はincludeとして提供される実装、"C"は本体ソース内にのみある実装の用な気がする。

createMeshの最低限

だいたいこんな感じ。

    bool 
      CMQOMeshFileLoader::isALoadableFileExtension(
          const io::path& filename) const
      {
        return core::hasFileExtension ( filename, "mqo" );
      }

    IAnimatedMesh* 
      CMQOMeshFileLoader::createMesh(io::IReadFile* file)
      {
        const long filesize = file->getSize();
        if (!filesize){
          std::cout << "empty file" << std::endl;
          return 0;
        }

        // 実際のジオメトリ保持する。マテリアル毎に作る
        SMeshBuffer *meshBuffer=new SMeshBuffer;

        // 頂点の追加(最大で65535)
        video::S3DVertex vertex;
        vertex.Pos.set(v.x, v.y, -v.z);
        vertex.TCoords.set(uv.x, uv.y);
        vertex.Color.set(rgba.a, rgba.r, rgba.g, rgba.b);
        meshBuffer->Vertices.push_back(vertex);
        // インデックスの追加(最大で65535)
        meshBuffer->Indices.push_back(meshBuffer->Indices.size());
        // マテリアル云々
        video::SMaterial &material=meshBuffer->Material;

        // IMeshの実装
        SMesh *mesh=new SMesh;
        mesh->MeshBuffers.push_back(meshBuffer);
        mesh->recalculateBoundingBox();

        // IAnimatedMeshの実装
        SAnimatedMesh *animMesh = new SAnimatedMesh();
        animMesh->Type = EAMT_UNKNOWN;
        animMesh->recalculateBoundingBox();
        animMesh->addMesh(mesh);
        mesh->drop();

        return animMesh;
      }

mqoを読ませてみた。