その8 位置の情報を取得する
ここまでの章は3Dモデルを表現する情報の取得でした。この章ではモデルの位置(姿勢)に関する情報を取得します。モデルの位置はワールド座標上に定義され、時間と共に変化します。つまり、この章の情報によりモデルの「アニメーション」が見えてきます。
@ 位置を取ることは簡単
イメージから入ります。3Dモデリングツールで立方体を次のように作成したとします:
制御点が立方体の中心にあり、(0, 2, 0)に位置しています。このモデルの情報を持つFBXファイルから位置を取得するにはKFbxNode::GetGlobalFromCurrentTakeメソッドを用います。ソースで書くと次のような感じです:
モデルの位置を取得する KFbxMesh* mesh; // メッシュオブジェクト(取得して下さい)
KTime time; // 時間変数(後述)
KFbxNode* node = mesh->GetNode();
KFbxXMatrix pos = node->GetGlobalFromCurrentTake( time );
まずKFbxMeshオブジェクトをシーンから取得します。これはその4をご覧下さい。メッシュを抱えているノードを次に取得します。このノードがメッシュの位置情報を持っています。位置を取得する時に「時刻」をKTimeオブジェクトで指定する必要があります。これが実は肝なんです。
位置の取得自体は上の数行で終わりなので簡単です。引数に時刻を渡せるので、アニメーションしているモデルの任意時刻での位置もこれで取れます。では「時刻(KTime)」とは何を与えればよいのでしょうか?
A 時刻の扱い
多くのゲームでは時刻はフレームとして与えます。アニメーションしているモデルの位置もフレーム単位で考えるのが普通です。では上でのKTimeオブジェクトがフレームかというと、これが全然違う概念なんです。
FBX SDKで扱う時刻は「64bitの整数」です。型で言うとLONGLONG型です。42億の42億倍という良くわからない大きさの数字ですが、これで世界の時刻を表します。1秒がいくつというのが決まっているのかそうでないのかは良くわかりませんが、24フレームを1秒とするアニメーションの場合1秒は「4,618,615,800」でした。試しに60フレームを1秒とするアニメーションでテストしてみると1秒は同じく「4,618,615,800」。決まっているっぽい気もしますが、ちゃんと数値を貰ったほうが良いです。
KTimeはこのように膨大に大きな数字を保持しているわけですが、こちらからこの数値を与えることは稀です。その代わりに「時分秒」もしくは「フレーム」で時刻を指定できます。それを設定するのがKTime.SetTimeメソッドです:
KTime::SetTimeメソッド void KTime::SetTime (
int pHour,
int pMinute,
int pSecond,
int pFrame = 0,
int pField = 0,
int pTimeMode = eDEFAULT_MODE,
double pFramerate = 0.0
)
pHour、pMiniute、pSecondはそれぞれ時、分、秒を与えます。int型なので整数です。
pFrameはいわゆるフレーム数を与えます。
pFieldは・・・何でしょう、良くわかりませんが時刻を与えるものの一つだと思います。知らないものは使わないです(^-^;。
pTimeModeは「タイムモード」を列挙型で指定します。このタイムモードによって与えた時刻と内部の64bit整数値が変わります。
pFramerateはpTimeModeがTime::eCUSTOMだった時に与えることができるカスタムフレームレート値です。
時分秒とフレーム数を同時に与えると、それを全部足した時刻が内部に設定されます。混乱しないように通常は時分秒かフレーム値のどちらかのみを使うと思います。pTimeModeに与えるタイムモードは非常に沢山ありますが、ゲームに使いそうな所を以下に挙げます:
列挙型 フレーム数 1フレームの64bit値 KTime::eCINEMA 24fps 1,924,423,250 KTime::eFRAMES30 30fps 1,539,538,600 KTime::eFRAMES60 60fps 769,769,300
これを見るとタイムモードによって1フレームの時刻である64bit値が異なるのがわかります(当然ですが)。1/60秒が7.7億くらいですから、どんな解像度なんだと思います(^-^;。
上表の数値を得るコードはこのような感じです:
1フレーム時間を設定(60fpsの場合) KTime period;
period.SetTime( 0, 0, 0, 1, 0, KTime::eFRAMES60 );
何の話をしていたか忘れてしまったかもしれませんが、ある時刻のモデルの位置を取得するのが目的でした。その「ある時刻」はKTimeオブジェクトで、ゲームで使うならば「1フレーム単位の時刻」を与える必要があります。その設定方法が上記です。
「お!これでもう位置は取れるじゃん。」と思った方。おしい!このままでは駄目です。なぜなら、「FBXに格納されているアニメーションのタイムモード」を得なければ1フレーム時間が変わってしまうからです。Mayaのデフォルトは24fps/1secです。これを60fpsだとして上のようなKTimeオブジェクトを作って位置を取ると、スローモーションのような動きになってしまいます。
FBXに設定されているタイムモードを取るには2段階を踏みます。まずKFbxScene::GetGlobalTimeSettingsメソッドでグローバルタイム設定オブジェクトを得ます(KFbxGlobalTimeSettings)。次にKFbxGlobalTimeSettings::GetTimeModeメソッドでタイムモードを得られます:
タイムモード取得 KFbxGlobalTimeSettings &globalTimeSettings = scene->GetGlobalTimeSettings();
KTime::ETimeMode timeMode = globalTimeSettings.GetTimeMode();
これでようやく、1フレーム単位で位置を取得することができるようになりました。例えば10フレーム目の位置を取得するには次のようになります:
10フレーム目のモデル位置を取得 KFbxGlobalTimeSettings &globalTimeSettings = scene->GetGlobalTimeSettings();
KTime::ETimeMode timeMode = globalTimeSettings.GetTimeMode();
KTime period;
period.SetTime( 0, 0, 0, 1, 0, timeMode );
KFbxNode* node = mesh->GetNode(); // meshはKFbxMesh
KFbxXMatrix pos = node->GetGlobalFromCurrentTake( period * 10 );
KTimeクラスは加算や乗算などの各種演算子を定義しています。
B アニメーションフレーム数を取得する
やれやれこれでおしまい・・・ではありません。モデルが持つ1モーション分の位置を取らないとモデルは動けません。Aまでで1フレーム単位での位置を取得することはできていますので、後は「何フレーム取ればいいのか?」がわかれば良いわけです。ただ、これが中々に遠回りだったりします。
アニメーション時間はKFbxTakeInfoクラスに格納されています。このオブジェクトを得るには、KFbxScene::GetTakeInfoメソッドを用います。シーンがオブジェクトを持っているわけです。そして、このGetTakeInfoメソッドの引数には「テイクの名前」を与えます。
テイクというのは映画などの「テイク」と同じで、シーンのある部分を切り取ったものです。FBXの1シーンにはいくつものテイクを含めることができますが、一般には扱いやすくするために1テイクだけ含めます。FBXに含まれているテイクを調べ、その名を与えれば、もれなくテイクの時間であるアニメーションフレームに関する情報を得ることができます。
テイク名はKFbxScene::FillTakeNameArrayメソッドで取得する事ができます。得たテイク名をKFbxScene::GetTakeInfoメソッドに渡してKFbxTakeInfoオブジェクトを得て、そこからアニメーション時間を得ます。ソースを見た方が早いので以下をご覧下さい:
アニメーションフレーム数を取得する KArrayTemplate< KString* > takeNameAry; // 文字列格納配列
scene->FillTakeNameArray( takeNameAry ); // テイク名取得
int numTake = takeNameAry.GetCount(); // テイク数
bool isTakeExist = false;
for ( int i = 0; i < numTake; ++i ) {
// テイク名からテイク情報を取得
KFbxTakeInfo* currentTakeInfo = scene->GetTakeInfo( *( takeNameAry[ i ] ) );
if ( currentTakeInfo )
{
KTime start = currentTakeInfo->mLocalTimeSpan.GetStart();
KTime stop = currentTakeInfo->mLocalTimeSpan.GetStop();
isTakeExist = true;
break;
}
}
// 1フレーム時間(period)で割ればフレーム数になります
int startFrame = (int)( start.Get() / period.Get() );
int stopFrame = (int)( stop.Get() / period.Get() );
DeleteAndClear( takeNameAry ); // これを呼んで配列をクリアしないとリークが起こります!
まずKArrayTemplate<KString*>という配列テンプレートを宣言してテイク名を格納する文字列配列を定義します。これをKFbxScene::FillTakeNameArrayメソッドに渡すとテイク名が格納されます。配列の要素数はGetCountメソッドでわかりますので、これでループを回し、KFbxTakeInfoオブジェクトをシーンから得ます。テイクには「デフォルトテイク」がありまして、ここからはKFbxTakeInfoオブジェクトを得ることができません(NULLが返ります)。currentTakeInfoの存在を判定しているのはそのためです。存在している場合、KFbxTakeInfo::mLocalTimeSpan.GetStartメソッド及びGetStopメソッドで開始時刻と終了時刻を得ることができます。この時刻は64bitの整数なので、1フレーム時間(Aで取得したperiodです)でそれを割れば、時刻をフレーム数に変換できます。欲しいアニメーションフレーム数はstopFrameです。
アニメーションフレーム数が取得できれば、1フレームごとの位置(姿勢)をフレーム数分だけ取得することができます。ここに、FBXファイルからモデルのアニメーション情報を取得することできるようになりました〜。
これで、ボーンの入っていない固定モデルのアニメーションはばっちりできます。大変なのは時刻の扱い部分だと思いますので、ここをクラス等で扱って隠蔽してしまえば、アニメーションが随分と楽になります。次章ではいよいよボーンが入ったモデルの扱いです。これは・・・かなりごっつく面倒な部分です。