前回『
Colladaファイルビュアーを作成する(3)』に引き続き、今回はColladaファイルからジョイント(ボーン)情報を抽出し、画面に出力する方法について確認する。解析用Colladaファイルを引き続き利用するので、ダウンロードしていない方は『
Colladaファイルビュアーを作成する(2)』から入手して頂きたい。
■ ジョイント情報を読み込む
ジョイントとは人間で言うところの関節にあたるものである。肩の関節が回れば腕が動くように、肩のジョイントが回れば、腕のメッシュが回転する。ただ、ジョイントに対応してメッシュが動くという機能(スキニング機能)はOpenGLやDIrectXの標準APIにはない機能なので、自分で作成するか対応するライブラリを用いる必要がある。
スキニングの仕組みは簡単で、ジョイントが動いた際(平行移動・回転など)にジョイントに関連付けられたメッシュ上の頂点についても同じだけ動かせばいい。下図(の上の例)で言うと、ジョイントaが45度動いた場合、ジョイントb/cはジョイントaを中心に45度動く。これに伴って、ジョイントaを中心に腕のすべてのメッシュを45度動かすのである。
ただし、スキニングで注意が必要なのは軟体の移動である。先程はジョイントaが45度動いたら腕も45度動くといったが、脇部分も45度動かしてしまうと上図(の下の例)のように脇が直角になってしまうのである。望みとしてはジョイントaの動きに連動してちょっと動いてほしい(上図の上の例)。
では、どのようにして軟体の動きを再現するのか。現在の主流となっている手法としては、複数のジョイントの動きの加重平均を取るといったものである。脇の例で言うと、『ジョイントaの動きの3割、ジョイントbの動きの7割』動くように設定すると、動かないジョイントaと45度動くジョイントbの加重平均である31.5度動くのである。実際には脇のメッシュ頂点は複数あるので、各頂点で割合をずらしながら設定するのだが、これにより上図(の上の例)のような動きが可能となる。
■ ワールド座標系とローカル座標系
Colladaファイルでのジョイント情報の読み込みのため、ワールド座標系とローカル座標系という概念を導入する。座標系とは『ある点を数値を用いて表す決まり』のような意味である。緯度と経度も座標系の一種なので、緯度・経度を用いて説明する。
例えば、いま座標系を『北海道を中心とする』(北海道座標系)としよう。方向は(北,東,南,西)という4次元ベクトルで表示することとし、北東ならば( 1, 1 , 0 , 0 )のように表すこととする。すると、あなたが北海道にいる場合には、東京は南( 0 , 0 , 1 , 0 )で、大阪は南西( 0 , 0 , 1 , 1 )である。次に、飛行機に乗って『東京を中心とする』座標系(東京座標系)に移動したとする。すると、東京は中心( 0 , 0 , 0 , 0 )であり、大阪は西( 0 , 0 , 0 , 1 )となる。このように座標系は定義した中心地点の数だけ存在する。この無数にある座標系をローカル座標系という。また、北海道座標系から『飛行機に乗って』東京座標系に移動するように、ローカル座標系間は移動することができる。これを座標変換という。
一方、プログラミング(OpenGL,DirectX,JavaFXなど)では、ポリゴンを作成すると( 0 , 0 , 0 )という座標に配置される。この( 0 , 0 , 0 )を中心とした座標系をワールド座標系という。各ポリゴンの位置はすべて、このワールド座標系で指定することになる。
ここで例えば、( 0 , 0 , 0 ),( 1 , 0 , 0 ),( 0 , 1 , 0 )の3点で構成される三角形を考える。この三角形をワールド座標系で移動させることを考えると、例えば「x軸方向に1ずらす」程度の移動であればx軸の座標を1加算するだけでいいが、「三角形の重心を中心に垂直方向に90度回転させる」などの複雑な移動については、それぞれの頂点位置を計算するのは少々煩雑となる。そこで利用するのがローカル座標系である。ローカル座標系でポリゴンの形(各頂点の位置関係)を定義しておく。そして、このローカル座標系自体をワールド座標系でどのように配置するか(ローカル座標の中心をどこに置き、どれだけ回転、拡大・縮小させるか)を変化させることにより、ポリゴンの位置もそれに付随して変化させるという方法にすると計算(というか考え方)が楽になる。このため、3Dグラフィックではワールド座標系とローカル座標系という考え方がよく利用されている。
■ Colladaファイル上でジョイント情報を確認する
※解析用Colladaファイルのメッシュ(左)と、ジョイントおよびボーン(右)
Colladaファイルでは各関節の情報は親子関係のツリーとして表現され、位置情報は親関節からの相対位置として記述される。親子関係とは隣あう関節間に設定されるもので、親が動くと子が連動して動く関係を言う。肩と肘の関係に例で言うと、肩が動くと肘が動き、その逆はないので肩が親で肘が子となる。このため、ワールド座標系における関節の位置は、以下の順番で計算していく。各関節は各座標系の中心で表わされる。
- ワールド座標系→関節ツリーのルート関節座標系への座標変換
- ルート関節座標系→ルートの子関節座標系への座標変換
- (親子関節の数だけ座標変換を繰り返す)
実際に解析用Colladaファイルを見てみる(2038行目~2046行目)。以下は関節ツリーのルートノード付近の記述である。
<node id="l_a_body" name="l_a_body" type="NODE">
<matrix sid="transform">1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1</matrix>
<node id="center" name="center" sid="center" type="JOINT">
<matrix sid="transform">1 3.16127e-7 -8.91658e-7 9.79568e-4 -8.91557e-7 -3.19525e-4 -0.9999999 -0.06472462 -3.16412e-7 0.9999999 -3.19525e-4 1.358388 0 0 0 1</matrix>
<node id="upper_body" name="upper body" sid="upper_body" type="JOINT">
<matrix sid="transform">-1 -4.16906e-7 -1.55743e-6 -2.4715e-7 -2.34814e-7 0.9933498 -0.1151355 0.9559246 1.59507e-6 -0.1151355 -0.9933498 0.05099178 0 0 0 1</matrix>
<node id="shoulder_L" name="shoulder_L" sid="shoulder_L" type="JOINT">
<matrix sid="transform">-0.02601654 -0.9906281 -0.1340865 -0.08280444 0.03899945 -0.1350355 0.9900731 0.5147643 -0.9989005 0.02052898 0.04214711 -0.004594356 0 0 0 1</matrix>
<node id="arm_L" name="arm_L" sid="arm_L" type="JOINT">
・・・
1行目はメッシュ全体を現すノードで、3行目に関節のルートノード(ID=center)が宣言されている。2行目はワールド座標系からメッシュへの座標変換用の情報で変換行列と呼ばれる。変換行列についての説明は数学の領域なので割愛するが、JavaFXで言えばこの情報をAffineクラスに渡すことで座標変換を行うことができる。詳しくはサンプルプログラムを参照。
ルート関節の中には、変換行列(4行目)と子関節(ID=upper_body)が宣言されている(5行目)。ルート関節をワールド座標系に直すには、『ワールド座標系→メッシュの座標系』の座標系変換をした後に、『メッシュの座標系→ルート関節座標系』の変換を行えばよい。基本的には、この親子関係が延々と続くので、ツリー構造として解析していけばよい。
■ サンプルプログラム
以下ではColladaファイル内のジョイントとボーンを描写するJavaFXプログラムを示す。ボーンとはジョイント間を繋ぐ線のことである。ジョイントとボーンは通常描写するものではないが、情報を分かりやすく可視化するため画面上に出力する。今回からはプログラム行数が多少多くなるので、ファイルをアップロードする。
◇ サンプルプログラム
◇ フォルダ構成(eclipse)
プロジェクト・ルート
┣ src
┃┣ collada2
┃┃┣ Test3DModelImport.java
┃┃┣ Dae141FileLoader.java
┃┃┣ ColladaData.java
┃┃┣ DaeNode.java
┃┃┗ JointBone.java
┃┃
┃┗ org.collada._2005._11.colladaschema ← JAXBで自動生成したクラス郡
┃ ┗ ・・・
┃
┗ 3dmodel
┗ Luka
┗ luka1_0_1.dae
◇ 実行結果
◇ 解説
サンプルではvisual_sceneノード内に存在するNodeノードだけを抽出している。ソース解読の注目ポイントは以下の通り。ファイルの解析箇所はかなりの力技になっている。
- (Test3DModelImport.java:58行目)Colladaファイルを解析し、Colladaインスタンスを作成している。
- (Dae141FileLoader.java:49行目~62行目)Colladaファイルの解析はJAXBで、データをインポートすることからはじめる。
- (Dae141FileLoader.java:66行目、77行目~121行目)JAXBデータの解析は、ルートノードから階層を降りながら実施。解析したデータはColladaDataインスタンスに登録し、ID指定で呼び出せるようにMapにも登録していく。
- (Dae141FileLoader.java:109行目、128行目~177行目)ジョイント情報を含むnodeノードの解析を再帰的に実行。単なるNodeノードはDaeNodeクラス、関節を表すNodeノードはJointBoneクラスとして作成している。
- (Dae141FileLoader.java:161行目~167行目)matrixノードに記述された4x4個のデータから変換行列(親からの相対位置)を作成。
- (Dae141FileLoader.java:161行目~167行目)matrixノードに記述された4x4個のデータから変換行列(親からの相対位置)を作成。
- (Test3DModelImport.java:190行目~202行目)関節間に親子関係を設定。親の変換行列(ワールド座標系→親関節の変換)を設定。
- (ColladaData.java:27行目~37行目、52行目~110行目)ColladaDataインスタンスに登録された関節情報を取得し、再帰的に描画していく。
ジョイントとボーンだけでは分かりにくいので、メッシュを重ねたイメージは以下のとおり。メッシュ上で動きそうな箇所にジョイントが設定されているのが分かる。
※上記ジョイント+ボーンにメッシュを重ねたイメージ
次回はメッシュの取り込みについてみていく。