unittest重要w

ousttrue2009-04-20

やっとmkmの読み込み処理が完成した。
しかし、恐ろしい試練だった。
要は、クォータニオンを右からかけるか左からかけるかという
一貫性の問題だったわけだがこれを修正するのがえらく難航。
DirectXとかを使っていれば多分起きない問題だが、
自前で数学ライブラリを作ったためにハマったのであった。
ベクトル・行列・クォータニオンのコードを必要になるたびに検索して
適当に作っていたために、
クォータニオンのかける順番(どっちでもいいと思ってましたw)とか、
右と左どっちが共役なのかとか、
そも右と左のどっちのクォータニオンを積算しているのかとか、
外積の符号とかが一致していないところがあり
これらが絡みあってカオスになっていた。
しかもひとつ直すと他のところがおかしくなるというもぐら叩きの様相を呈して
グダグダ。
で、如何にして修正したかと言えば、cppunitを使った。
地道にテストケースを書いてボトムアップで少しずつ信頼性を確保したのです。


2週間くらいずっとハマっていたので疲れたが、
クォータニオンのみならず回転行列界隈の経験値が高まった。
適当なタイミングで数学的な理解を追随できたのは収穫だったか・・・


コード抜粋
ジオメトリ情報(mqo)からbone情報を計算(mkiと一致したら正解)

void
Skeleton::calcInitialParameter_(NodePtr object, BonePtr bone
    , const Vector3 &parentHead
    , const Quaternion &parentQuaternion
    )
{
  const Vector3 &head=object->vertices[bone->headIndex];
  const Vector3 &tail=object->vertices[bone->tailIndex];
  const Vector3 &up=object->vertices[bone->hIndex];
  Vector3 zaxis=(tail-head).createNormalized();
  Vector3 xaxis=Vector3::cross(up-head, zaxis).createNormalized();
  Vector3 yaxis=Vector3::cross(zaxis, xaxis);
  // srot算出
  Quaternion xQuaternion=Quaternion::createFromTo(
      Vector3::xaxis()
      , xaxis
      );
  Quaternion zQuaternion=Quaternion::createFromTo(
      Vector3::zaxis().apply(xQuaternion)
      , zaxis
      );
  Quaternion quaternion=xQuaternion * zQuaternion;

  bone->initialRotation=quaternion;
  bone->length=(tail-head).norm();
  // spos
  bone->relativePosition=(head-parentHead).apply(parentQuaternion.createInverse());

  for(BonePtr current=bone->child; current; current=current->sibling){
    calcInitialParameter_(object, current
        , head, quaternion
        );
  }
}

ボーン情報(mqoとmki)とキーフレーム情報(mkm)から組み立て

  void drawSkeleton_(NodePtr object, BonePtr bone, int frame
      , const Vector3 &parentHead
      , const Quaternion &parentInitialRotation
      , const Quaternion &parentRotation
      )
  {
    Quaternion mkm=getQuaternion_(bone, frame);

    Vector3 head=parentHead + bone->relativePosition.apply(
        parentRotation * parentInitialRotation);

    Quaternion quaternion=
      bone->initialRotation.createInverse()*mkm*parentInitialRotation;

    Vector3 tail=head+Vector3::zaxis().apply(
        quaternion
        * bone->initialRotation
        ) * bone->length;

    drawABone_(head, tail);

    for(BonePtr current=bone->child;
        current;
        current=current->sibling)
    {
      drawSkeleton_(object, current, frame
          , head , bone->initialRotation, quaternion
          );
    }
  }

少し長くなってしまったが全コード。
http://wiki.github.com/ousttrue/mkm