ホーム < ゲームつくろー! < DirectX技術編 < オブジェクトを任意の平面に立たせる姿勢制御
その16 オブジェクトを任意の平面に立たせる姿勢制御
ローカル座標にあるオブジェクトをワールド座標に持っていく時には、オブジェクトをどう回転させてどう平行移動させるかを常に考える必要があります。平行移動は制御点の移動であり、それほど問題にはならないのですが、面倒なのがオブジェクトの回転です。例えば、ジャンボジェット機をある方向に飛ばすとき、方向指示だけでは地面に対してまっ逆さまに飛んでしまうかもしれませんし、アクロバット飛行のように前に進みながらぐるぐる回転するかもしれません。方向の指示だけでは、機体をどういう姿勢に保っておけば良いか決まらないわけです。これでは中に乗っている人は大変です(笑)。安定飛行中出れば、飛行機は地面に平行に飛んでくれるのが理想ですよね。この時、私たちは暗黙のうちに「方向(3次元ベクトル)」に加えて「上方向」というもう1つの方向を思い描いています。姿勢を制御するには進みたい方向と上の方向、2つの方向性が必要なんです。
この章では、ローカル座標にあるオブジェクトをワールド座標に変換する時の姿勢の決定、姿勢の制御について考えて見ます。
@ 平面に立つとは
私たちが地面に立つ時には3つの要素が必要になります。1つは平面の上に足を乗せること。つまり、平面に制御点がふれている必要があります。2つ目は、重力と反対方向(上方向ベクトル=姿勢ベクトル)に頭を向けることです。そうしないと、地面に斜めに立つことになってしまいます。3つ目は方向回転です。これは、通常どこを向いても大丈夫なものです。平面にキャラクタを立たせるには、この3つの要素を常に意識します。
平面に触れる制御点は探せば見つかります。進行方向は任意です。それに対して、姿勢ベクトルは任意ではありません。ワールド空間にある上方向と、キャラクタの上方向を合わせる必要があります。もし、ワールド空間の上方向が常にY軸方向で、キャラクタのローカル座標も同じ状況であるなら、お話はめちゃくちゃ簡単です。最初から両方の座標系の上方向が揃っているんですから、「平面に立つ」という作業は、実質制御点を平面に触れさせると言う作業だけになります。
問題は上方向が空方向で無いときです。例えば、ジェットコースターのようにひねったり回転したりするコースがあって、台座キャラクタがそのコースに沿わなければならない場合、空方向にキャラクタを向けるとまったくうまくいきません。ワールドの上方向は空であっても、その時のオブジェクトの上方向は自分が触れている平面の「法線ベクトル」になっているからです。こういう場合、オブジェクトの上方向を平面の法線と同じくなるようにキャラクタを回転させる作業が必要になってきます。ここでお話しする内容は、主にこちらの方の話になります。
A 方向と姿勢の2回合わせ
姿勢を制御するためには、平面の法線ベクトルとオブジェクトの座標系の上方向(ローカル座標なら通常Y軸)を合わせ、さらにそれを保ったまま方向ベクトルも定めなければなりません。つまり、姿勢→方向の2段階の方向合わせが必要になるわけです。どういうことか、図をご覧下さい。
今ローカル座標にあるローカルオブジェクトをワールド空間にある平面にぴたりと沿わせる(立たせる)事を考えます。そのために、まず平面の立たせたい位置までローカルオブジェクトを平行移動させます。
次に、姿勢を表すベクトル(黄色)を平面の法線ベクトル(赤)と重なるようにオブジェクトを回転させます。この回転には「任意回転軸」が必要です。回転軸はオブジェクトの姿勢ベクトルと平面の法線ベクトルに垂直になります(2つのベクトルを同一平面に置く常套手段です)。その回転軸を用いて然るべき角度だけ回転させたのが下の図です。
黄色いベクトルと赤い法線ベクトルがぴったり重なっているのが分かると思います。実際、これで平面にはもう立っている状態になります。ただ、青い方向に進ませるためには、オブジェクトをそちらの方向に向ける必要があります。これは、黄色いベクトル(=平面の法線)を軸としてぐるっと180度回転させれば良いわけです。そうすると、次の図のようになります。
これで、見た目にもお分かりの通り、平面にしっかりと立って進むべき方向にキャラクタが向いています。この作業をDirectXの座標変換で行うのが、この章の目的です。
B クォータニオンによる軸回転
Aで説明した回転はある軸を中心とした「任意軸回転」です。これを簡単に行うにはクォータニオンを用います(クォータニオンの詳しい計算方法についてはDirectX技術編その10『クォータニオンを学んでみよう』を参照してください)。回転クォータニオンを求めるには「軸のベクトル」と「回転角度」が必要です。Aでは2回の軸回転がありました。1回目を「姿勢合わせ回転」、2回目を「方向合わせ回転」としておきますと、それぞれの軸と回転角度は次のようになります。
軸 回転角度 姿勢合わせ回転 オブジェクトの上方向ベクトルと平面の法線に垂直なベクトル オブジェクトの上方向ベクトルと
平面の法線ベクトルのなす角度方向合わせ回転 平面の法線ベクトル 姿勢合わせ回転後のオブジェクトの方向ベクトルと
平面に平行なベクトルのなす角度
殆どのベクトルは回転前に与えられますが、1つ「姿勢合わせ回転後のオブジェクトの方向ベクトル」だけは、姿勢合わせ回転の後でなければ得ることが出来ません。少しずつ噛み砕いていきましょう。
○ 姿勢合わせ回転
姿勢合わせ回転で1つ算出を要するものは2つのベクトルに垂直なベクトルです。そのベクトルが軸となります。2つのベクトルに垂直なベクトルは外積で簡単に算出できます。今、オブジェクトの上方向ベクトルをOU、平面の法線をNとしますと、姿勢回転軸ベクトルAx1は、
Ax1 = OU×N
となります。掛ける順番に注意してください。これを逆にすると、逆向きの法線が算出されてしまい、回転がうまく行きません。
回転角度R1は内積から算出できます。
cos(R1) = OU・N/(|OU||N|)
∴ R1 = ACOS(OU・N/(|OU||N|))
回転軸の法線ベクトルと回転角度が揃ったら、回転クォータニオンを作成します。これは自前でも良いのですが、ヘルパー関数を用いましょう。
D3DXQUATERNION Q1(0,0,0,1);
D3DXQuaternionRotationAxis( &Q1, &Ax1, R1 );
これで1つ目の回転に必要な計算は終わりです。
○ 方向合わせ回転
続いて方向合わせ回転を考えます。まず、必要な軸ですが、これはありがたいことに平面の法線ベクトルですから、もう与えられています。回転角度を求めるためには、「姿勢合わせ後のオブジェクトの方向ベクトル」というのが必要になります。これは姿勢合わせ回転で求めた回転クォータニオンをローカル座標内のオブジェクトの方向ベクトルに適用するだけです。ローカル座標におけるオブジェクトの方向ベクトルを回転クォータニオンQ1で回転させるには、次のようにします。
D3DXQUATERNION CQ1;
D3DXQuaternionConjugate( &CQ1, &Q1 ); // 共役クォータニオン
D3DXQUATERNION ODQ( 0, 0, 1, 0); // ローカルの方向クォータニオン(Z軸)
D3DXQUATERNION Res;
D3DXQuaternionMultiply( &Res, &CQ1, &ODQ ); // 共役・ベクトル・Q1
D3DXQuaternionMultiply( &Res, &Res, &Q1 );
D3DXVECTOR3 OD; // 姿勢合わせ回転後方向ベクトル
OD.x = Res.x;
OD.y = Res.y;
OD.z = Res.z;
これで、姿勢合わせ回転後のオブジェクトの方向ベクトルが求まりました(Aの図のオレンジのベクトルです)。あとは、このベクトルODと、平面に平行な進行方向ベクトルとがなす角度R2を内積で計算し、回転クォータニオンQ2を作るだけです。Q2は次のように計算されます。
D3DXQUATERNION Q2(0,0,0,1);
D3DXQuaternionRotationAxis( &Q2, &N, R2 );
○ 合成クォータニオンから回転行列を作成
これで2段階の回転ができました。クォータニオンの便利な性質として、2つの回転クォータニオンを合成して1つの合成クォータニオンにすることができます。これは、単にクォータニオン同士を掛け算をするだけです。
D3DXQUATERNION Q;
D3DXQuaternionMultiply( &Q, &Q2, &Q1 );
掛ける方向に注意してください。逆にすると、Q2回転させてからQ1回転になってしまいます。こうして求めた合成回転クォータニオンQからDirectXで扱える回転行列を作成します。これは、ヘルパー関数を用います。
D3DXMATRIX RotMat;
D3DXMatrixRotationQuaternion( &RotMat, &Q );
これで、ローカル座標にあるオブジェクトを平面に立たせて任意の方向に向かせるための回転行列が完成しました!!
3D空間の任意の位置で特定の姿勢を取らなければならないという状況は、実は多くはないのですが、上記の方法はそういう状況で必要です。2つのベクトルによって姿勢は固定されるというという事がまず大切です。これを少し発展させたのがDirectXのカメラの姿勢制御です。カメラの上方向は空に向けまして、カメラの位置と注視点が方向ベクトルに当たります。ただ、今回の例と違うのは、上方向のベクトルと方向ベクトルが直行していないという点です。それでも、姿勢は制御できます。これは、方向ベクトルと上方向ベクトルによってできる平面を利用しています。これについては…またいずれですね(^-^;