三次元日誌 RSSフィード

2009-04-20

ousttrue2009-04-20

[][][]unittest重要w

やっと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

2009-04-15

[][][]掌性が違う

D3DXQuaternionRotationAxisは関係なかった。

180度も関係ない。

うまくいかない原因は掌性の食い違いにありそうだ。

mqoが右手系なんで迂闊にもmkmも同じ掌性だとばかり思っていたがそうではないようだ。

mikotoはたぶんDirectXで書いているので左手系を使っておりますな。

おそらくX軸に-1をかけている。

だからmkmに書いてあるクォータニオンはmqoからはXが反転された

左手系として扱う必要がある(読み込み時にx値に-1をかければOKか?)

追記

掌性の違いは関係ないのでクォータニオンの回転をいじる必要は無い。

http://sequoia-web.hp.infoseek.co.jp/tsudoi/tsudoi06.shtml

後でソースを読む

http://rogiken.org/vr/index.php?Mikoto2X

[][][]180度の回転がうまくいかない

mikoto形式のボーン読み込みは概ねできたのだが、

ボーンの第3の点(H点)が親と反対側についたときに生じる

Z軸に対する180度回転を含む回転がうまく解決できない。

mikotoに記述されているクォータニオンと違う値になって

あらぬ方向に曲がってしまう。

180度回転のときは外積から回転軸を特定できないので

特別処理が必要なのはわかったのだが、

具体的に使う値がわからず。

ToDo

後でDirectXのD3DXQuaternionRotationAxisの動作を確認する。

たぶんこの関数の返り値と同じものを返すようにすればうまくいく。

2009-04-12

[][][]mikotoのキーフレーム復元に成功

クォータニオンに不慣れなのもあって大きく手間取ったが

なんとか前進。


まだ、浮動ボーンの扱いなどにおかしなところがあるようだが、mikotoフォーマットについて忘れないうちにメモしておく。


mkmファイルのQuaternionに記録されているキーフレーム値

# 例
0 (0.002538 0.000000 0.000000 0.999997)

は、回転クォータニオン(x, y, z, t)を記録している。

((t, x, y, z)ではない)

この回転クォータニオンには、

「現フレームのボーンのZ軸からの回転状態」から

「一つ前のフレームのボーンのZ軸からの回転状態」への

回転同士の差分が格納されている。

式で書くと

Q_{n}^{-1} ¥cdot Q_{n-1}

となる。

Q_{n} ¥cdot Q_{n-1}^{-1}

何故このような形式になっているのかはここではまだ気にしない。


Q_{n}

は、ベクトル[0, 0, 1](Z軸。mikoto形式の基準軸)からベクトル[bone_end - bone_start]への回転クォータニオンとなる。

(さらに[0, 1, 0]から[h点 - bone_start]への回転の2軸をあわせる必要がある。)

外積で回転軸、内積で角度を求めて回転クォータニオンとする。

(ここで回転軸の正規化を怠って数日はまったw)


次にキーフレームからのボーン復元について。

(ここは不正確かもしれない)

これには、親ボーンについて

  • 始点
  • 回転状態
  • 初期回転状態

現ボーンについて

  • 親ボーンの始点からの相対位置
  • 回転状態
  • 初期回転状態

が必要だった。

それぞれの項目について少し説明しておく

親ボーンの始点

ルートから順番に処理していくことで確定してきた

ボーンのワールド座標における始点位置。ルートから再帰的に処理する必要がある。

初期回転状態(mkiのsrot)

ボーンをmqoから読み込んだ状態でのボーンの向き。

つまり[0, 0, 1](Z軸。mikoto形式の基準)から[bone_end-bone_start]への回転クォータニオン

これは、mqoを読み込んだときに求めておく。

もしくはmkiから読み込む。

回転状態

mkmのQuaternionに記録されている値から初期回転状態の影響を除いたもの。

式で書くと

I_{n}^{-1} ¥cdot Q_{n} ¥( I_{n-1}^{-1} ¥cdot Q_{n-1} ¥)^{-1}

となる(回転初期状態のクォータニオンをInitからIと表記)。

展開すると

I_{n}^{-1} ¥cdot Q_{n} ¥cdot Q_{n-1}^{-1} ¥cdot I_{n-1}

I_{n}^{-1} ¥cdot MKM ¥cdot I_{n-1}

となりmkmと親子の回転初期状態から求められる。

mqoを読み込んでいる状態から一切動かしていない場合は、アイデンティティクォータニオン(1, 0, 0, 0)となるはず。

MKMは、キーフレームの動きと初期回転状態を内包しているので

それから初期回転を取り除いている。

親ボーンの始点からの相対位置(mkiのspos)

自身の始点から親の始点をひいたもの。回転初期状態と一緒に求めておく。


以上の情報から

始点 = 親の始点 + (親ボーンの始点からの相対位置 * 親の回転状態)
終点 = 始点 + (Z軸 * 初期回転状態 * 回転状態) * (ボーンの長さ)

という感じでキーフレームのボーンを復元できた。

もっとよいやり方はありそうだが、スケルトン木構造をルートからたどりながら処理せざるを得ないと思われる。

これでようやく骨がポーズをとれるようになった。

(まだ、モデルと骨が結びついていないので骨だけ)

次のテーマは、キーフレームの補完かスキニングかな。

追加

ボーンの第3の点(H点というらしい)がY軸方向をあらわすっぽい。

おそらく始点->終点がZ軸なのに加えて

(始点->終点)X(始点->H点)がX軸。

求めた(Z軸)X(X軸)がY軸だと思う。

(Xは外積)

これで捻りを表現するということか。

現状だとZ軸しか一致させていないのでX軸かY軸についても一致させないと

不十分な様子。

ここが参考になりそう。

http://marupeke296.com/DXG_No21_BoneControlUsingQuaternion.html

追加2

明らかに間違っているところを直し

2009-04-09

[][][]頓挫中・・・

うぅむ。

勘違いしていそうなところがひとつわかった。

進めるかもしれん。

2009-04-07

ousttrue2009-04-07

[][][]スケルトンのツリー表示

モーションの続きを進める前にリファクタリングと表示強化。

ソースはこちら。

http://github.com/ousttrue/kugutsu/tree/master

wxWidgetsなのでビルドが面倒だけども