07、IK(回転、移動)
第7章ではマウスドラッグでボーンの姿勢を編集できるようにします。
まず座標系を導入しました。
ボーンの軸がZ軸と一致するような座標軸を導入し、それが視覚で分かるようにマニピュレータを表示します。
赤がX軸、緑がY軸、青がZ軸を示します。
マニピュレータを右ドラッグすることによりIKの計算を行い、ボーンの姿勢を編集します。
メインウインドウの「回転IK」「移動IK」のラジオボタンで回転か移動かを選びます。
回転の場合はラジオボタンの下の数字の表示のあるコンボボックスで計算階層数を指定します。
計算階層数は最大値であり、最大値に達する前に浮動ボーンや一番親に達した場合はそこで計算は止まります。
計算階層数00を選んだ場合は、浮動ボーンまたは一番親にたどり着くまで自動でさかのぼって計算します。
マニピュレータの中央の黄色をドラッグすると
自由な軸による回転をします。
赤、緑、青の部分をドラッグするとそれぞれ対応するX,Y,Z軸を回転軸にして回転します。
回転のIKはCCD方式です。
ソースは下のページからダウンロードしてください。
OpenRDBダウンロードページ
07−01、ボーン座標系の導入
今までの座標系はグローバル座標系でした。
しかし実際の姿勢の編集時にはボーンの軸の座標系で編集した方が効率がいいです。
そこでまずボーン座標系を導入します。
ボーンの軸がZ軸と一致し、後の2軸と垂直になるような座標系を考えます。
データとしてはこのような変換をするためのクォータニオンCBone::m_axisqを保持します。
CBone::m_axisqの計算方法は以下のようになります。
|
Z軸がボーンの軸と一致するようにし、Y軸はmikoto形式の三角ボーンの一番短い辺の頂点の位置を使って計算します。
そしてX,Y,Z軸がすべて垂直の関係になるようにCrossで調整します。
変換行列は新しいX軸が_11, _12, _13成分に、新しいY軸が_21, _22, _23成分に、
新しいZ軸が_31, _32, _33成分となるような回転行列です。
この行列をクォータニオンに変換してCBone::m_axisqに保存します。
座標系を設定したらボーン姿勢のオイラー角の計算も変えないといけません。
修正後のCQuaternion::Q2Eulは以下のようになります。
|
EQ = invaxisQ * *this * axisQ;
というところがミソです。
ボーン座標系でのクォータニオンに直しています。
axisQはCBone::m_axisqでinvaxisQはその逆クォータニオンです。
オイラー角からクォータニオンに直すときにも変更があります。
|
q = axisQ * qy * qx * qz * invaxisQ;
というところが修正部分です。
OpenRDBでのクォータニオンの掛け算の記述には注意が必要です。
回転変換の順番は掛け算記述と逆になります。
q1 * q2と書くとq2の回転のあとにq1の回転をするという意味です。
07−02、マニピュレータの描画
ボーン座標系に合わせて座標軸の向きをマニピュレータで選択ボーンの位置に表示します。
ボーンの選択は右クリックで行います。
左クリックじゃなくて右クリックなので注意してください。
マニピュレータはRDBMain.cppで作成表示します。
メタセコイアのファイルはselect_2.mqoです。Media/RDBMediaに入っています。
これをCModelであるs_selectに読み込みます。
一工夫いるのはカメラの距離などによってマニピュレータの大きさが変化しないようにすることです。
マニピュレータの描画部分のコードを貼り付けます。
|
マニピュレータのワールド変換行列を求めるのが少し難しいです。
同じ大きさにするための計算はselectscaleを求める部分です。
まずカメラ座標系でのボーンの位置とカメラ座標系でそこから(1.0, 0.0, 0.0)だけ離れた点を
スクリーン座標に変換します。
そしてスクリーン座標系での長さが一定になるようにselectscaleを計算します。
なぜカメラ座標系で2点を決めたかというと
カメラの向きに関わらず画面上の座標を決定できるからです。
07−03、マウスとオブジェクトのあたり判定
マニピュレータの表示が出来たら、それをマウスでクリックしたときに
マニピュレータのどの部分をクリックしたのかを判定しなければなりません。
そのための処理を説明します。
マウスでクリックした情報をRDBMain.cppのs_pickinfoに保持するようにします。
s_pickinfoはCoef.hで定義しているPICKINFO構造体です。
|
PICKINFO.buttonflagにはPICK_*で定義したenumのどれかが設定されます。
PICK_CENTERはボーンをクリックしたときに設定されます。
PICK_X,Y,Zはそれぞれ赤、緑、青の部分をクリックしたときに設定されます。
まずボーンを右クリックしたかどうかの判定方法を説明します。
ボーンのクリック判定はCModel::PickBoneで行います。
|
UpdateMatrixを実行するとCBone::m_childscreenにボーンのスクリーン座標がセットされます。
この値は画面中央が(0, 0)で画面内が-1から1の間に入るような数値です。
マウスでクリックした座標と比べるために、この-1から1の値をウインドウの大きさを考慮した
ウインドウの座標に変換します。
これをしてるのが
cmpsc.x = ( 1.0f + curbone->m_childscreen.x ) * fw;
cmpsc.y = ( 1.0f - curbone->m_childscreen.y ) * fh;
の部分です。
そしてボーンのウインドウ座標とマウスの位置の距離がPICKINFO.pickrangeより小さいボーンのうち
一番距離が小さいものを選びそのボーン番号をPICKINFO.pickobjnoにセットします。
マニピュレータの赤、緑、青の部分とマウスの判定はまたちょっと違います。
これはCModel::CollisionNoBoneObj_Mouseで行います。
|
今度は3Dのローカル座標系で判定します。
まずCalcMouseLocalRayでマウス座標を始点と方向のRayに変換します。
そしてそのRayとCMQOObject::CollisionLocal_Rayであたり判定をします。
CollisionLocal_Rayは、アンカーとRayとの判定方法と同じです。
07−04、自由軸のIK回転
マニピュレータの中央の黄色い部分を右ドラッグすると自由軸でIK回転するようにします。
IK回転のアルゴリズムにはCCD方式を採用します。
手順としてはまずマウスの位置からドラッグしたボーンの目標位置を求めます。
次にCCD方式に従って、ボーンの目標位置に近づくように子供から親にかけて順番に回転していきます。
この際どこまで親をさかのぼるかを指定できるようにしています。
メインウインドウの右下の数字の書いてあるコンボボックスでさかのぼる階層数を指定します。
00は特別で00を指定した場合は一番親に達するかもしくは浮動ボーンに到達するまでさかのぼります。
00以外でも途中で一番親に達したり浮動ボーンがあった場合はそこでストップします。
親をさかのぼっての回転作業は繰り返すほどボーンが目標地点に近くなります。
しかし回数が多くなると当然重くなるので今回は1回のIKにつき3回計算するようにしています。
ではまず目標座標の計算コードから見てみましょう。
目標座標はRDBMain.cppのCalcTargetPosで計算します。
|
CalcPickRayでマウス座標をRayに変換します。
まずスクリーン座標系で計算します。
マウス座標の変化分をボーンのスクリーン座標に足してRayのスクリーン座標のX,Yを求めます。
これがD3DXVECTOR3 mousescです。
次にRayの始点と終点のスクリーン座標startscとendscを求めます。
XとYはmousescと同じです。
スクリーン座標において、見える範囲の一番小さいZの値は0.0、一番大きいZの値は1.0です。
ですのでstartscのZを0.0にし、endscのZを1.0に設定します。
これでRayのスクリーン座標が求まりました。
あとはワールド座標にするためにビュー変換とプロジェクション変換の逆変換をします。
ビューとプロジェクションの変換行列をmVPに求め
この逆行列invVPを求めます。
これをD3DXVec3TransformCoordでスクリーン座標にかけてやると、ワールド座標が求まります。
このようにしてstartとendのワールド座標を求めます。
CalcTargetPosの残りの部分では、このRayとカメラ平面との交点を求め、これを目標座標とします。
Rayの式は
(1-t)OS + t OE
で表せます。
カメラ平面上の一点をボーンの位置Bとし、平面の法線をnとし、交点をT(target)とすると
平面の式は
BT・n = 0
(・は内積)
で表せます。
TはRay上の点なのでこのTにRayの式を代入してtを求めると
t = SB・n / SE・n
となります。
これをRayの式に代入して
求める交点、つまり目標座標が求まります。
目標座標が求まったらCModel::IKRotateでIK回転処理を行います。
|
スタンダードなCCD方式のIKです。
詳しく知りたい方は検索すればいくらでも説明は見つかるのでここでは省略します。
ポイントだけ言っておくと
計算は各ボーンの親のボーンの座標系で行うということです。
そのために計算で使う各座標に親のボーンの変換行列の逆行列をかけています。
07−05、指定軸のIK回転
マニピュレータの赤、緑、青の部分を右ドラッグした場合は
それぞれボーン座標系のX,Y,Z軸を回転軸とするIKを行います。
この指定軸のIK回転はCModel::IKRotateAxisDeltaで行います。
|
CCD方式を少し変更しています。
目標座標やボーンの位置を、親ボーンを通り指定軸を法線とする平面上に射影してから
それを親の座標系に戻して計算しいます。
こうすることにより回転軸が常に指定軸になります。
07−06、移動のIK
移動のIKはCModel::IKMoveとIKMoveAxisDeltaで行います。
IKMoveAxisDeltaも目標座標を決めた後にIKMoveを呼び出すだけなので
IKMoveだけを説明します。
|
この計算はローカル座標で行うためまず目標位置にワールド行列の逆行列をかけてtargetを求めます。
あとは実に力技ですww。
ボーンのローカル座標をorg、ボーンのローカルボーン変形行列をraw1、
IKによる位置移動行列をmvm、親のボーンの行列をpar1、ローカルの目標位置をtargetとすると
以下の式が成り立ちます。
org * raw1 * mvm * par1 = target
この式を各成分について展開して連立方程式を解き(紙とえんぴつで)、導いた式で移動成分を求めます。
この移動をボーンの姿勢に足して出来上がりです。
オープンソースのトップに戻る
トップページに戻る