2009年7月19日日曜日

読み込みまくるべし

Blenderで出力したファイルをC++で読み込み表示することになるわけですが、ただ読み込むだけではアニメーションさせることはできません。出力したファイルには行列やクォータニオンの値が含まれており、それらを使った計算が必要になります。これを手作りで実装するのは我ながらさすがに無謀なのでライブラリを使うことにします。ちょうどこの本(実例で学ぶゲーム3D数学)で使われているソースが使えそうだったのでそのまま使わせてもらいます。Vector3とかMatrix4x3とかQuaternionとかまさにうってつけのクラスが揃っています。

まず、アーマチャの読み込みですが次のような感じです。

void Model::readArmatureLine(std::istream& in) {
std::string name;

in >> name;
if (in.fail()) {
std::cerr << "failed in reading armature line" << std::endl;
return;
}
armature = new Armature(name);
Armature *a = armature;
in >> a->m.m11 >> a->m.m12 >> a->m.m13
>> a->m.m21 >> a->m.m22 >> a->m.m23
>> a->m.m31 >> a->m.m32 >> a->m.m33
>> a->m.tx >> a->m.ty >> a->m.tz;

if (determinant(a->m) > 0.00001f) {
a->isSingular = false;
a->im = inverse(a->>m);
} else {
a->isSingular = true;
a->im.identity();
}
}

Armatureクラスのインスタンスを生成して、読み込んだアーマチャ行列の各要素を取り込んでいます。a->mとa->imはそれぞれMatrix4x3クラスのインスタンスでimはmの逆行列です。Blenderで出力する時に逆行列は出力しなかったのでここで求めるようにしています。determinant()というのは上の本のソースで提供されている関数で行列の行列式を求める関数です。行列式が0の行列は逆行列を持ちません。そのため行列式の値を調べて逆行列の計算をしています。inverse()というのが逆行列を求める関数です。

ボーンの読み込みは次のような感じです。

Model::Bone* Model::readBoneLine(std::istream& in) {
static Bone *lastBone = NULL;
unsigned int level;

if (armature == NULL) {
std::cerr << "found bone line without previous armature line" << std::endl;
return NULL;
}
std::string name;
in >> level >> name;
Bone *b = new Bone(name);
in >> b->m.m11 >> b->m.m12 >> b->m.m13
>> b->m.m21 >> b->m.m22 >> b->m.m23
>> b->m.m31 >> b->m.m32 >> b->m.m33
>> b->m.tx >> b->m.ty >> b->m.tz;

if (determinant(b->m) > 0.00001f) {
b->isSingular = false;
b->im = inverse(b->m);
} else {
b->isSingular = true;
b->im.identity();
}

armature->addBone(lastBone, level, b);
lastBone = b;
return b;
}

アーマチャの読み込みと似たような感じですが、ボーンはアーマチャに属する(ぶら下がる)形になるため、ボーンの前にアーマチャが生成されていることが前提となります。そのためarmatureがNULLでないかどうか確認しています。そして読み込んだボーン行列の各要素をボーンに取り込んでいます。行列式の値を確認して逆行列を求めているところはアーマチャと同様です。そのあとボーンをアーマチャに追加しています。lastBoneとlevelは追加すべきボーンの階層位置を判断するためにaddBone()で使います。
Armature::addBone()の中身は次のような感じです。

void addBone(Bone *relBone, unsigned int level, Bone *bone) {
if (level == 0) {
if (root != NULL) {
delete root;
}
root = bone;
root->armature = this;
root->parent = NULL;
} else {
if (root == NULL) {
std::cerr << "no root bone set";
return;
}
assert(relBone != NULL);
Bone *parent = relBone->findAncestor(level-1);
if (parent != NULL) {
bone->armature = this;
parent->addChild(bone);
} else {
std::cerr << "not found parent of level=" << level-1;
}
}
}

level=0の場合はルートボーンと判断しています(Blenderでの作成時にルートボーンは1つだけという前提にしています)。それ以外は引数で渡されたrelBone(直前に読んだボーン)とlevelを元に自分がぶら下がるべきボーン(親)がどれになるかを判断して、そのボーンの子ボーンとして追加しています。findAncestor(level-1)は指定した階層になるまで遡って先祖を探すボーンのメンバ関数です。次のような内容です。

Bone* findAncestor(unsigned int level) const {
unsigned int curLevel = getLevel();
const Bone *bone = this;
while (bone != NULL && level != curLevel) {
bone = bone->parent;
--curLevel;
}
return (Bone *)bone;
}


...と長くなったので続きはまた。ちょっとややこしいですが、実装方法は人それぞれなので真似しなくていいです。^_^;
何にしても、もう少しで動き始めます。

0 件のコメント: